Created
September 27, 2024 21:05
-
-
Save 7etsuo/bfe1c62cb12447fa3b7c84be288337c5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// all ytdl files and code. | |
/* argument_parsing.c */ | |
#include "argument_parsing.h" | |
#include "help_display.h" | |
#include "directory_management.h" | |
#include <getopt.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <assert.h> | |
static char * | |
my_strdup (const char *s) | |
{ | |
assert (s != NULL); | |
size_t len = strlen (s) + 1; | |
char *new_str = malloc (len); | |
if (new_str == NULL) | |
return NULL; | |
return memcpy (new_str, s, len); | |
} | |
int | |
parse_arguments (int argc, char *argv[], Config *config) | |
{ | |
struct option long_options[] = {{"help", no_argument, 0, 'h'}, | |
{"output", required_argument, 0, 'o'}, | |
{0, 0, 0, 0}}; | |
int opt; | |
while ((opt = getopt_long (argc, argv, "ho:", long_options, NULL)) != -1) | |
{ | |
switch (opt) | |
{ | |
case 'h': | |
display_help (); | |
exit (EXIT_SUCCESS); | |
case 'o': | |
config->output_path = my_strdup (optarg); | |
if (config->output_path == NULL) | |
{ | |
fprintf (stderr, "Error: Memory allocation failed for output path\n"); | |
return EXIT_FAILURE; | |
} | |
break; | |
} | |
} | |
if (optind < argc) | |
{ | |
config->url = argv[optind]; | |
} | |
else | |
{ | |
fprintf (stderr, "Error: URL is required\n"); | |
display_help (); | |
return EXIT_FAILURE; | |
} | |
return 0; | |
} | |
int | |
initialize_output_path (Config *config) | |
{ | |
if (config->output_path == NULL) | |
{ | |
char *allocated_path = get_current_working_directory (); | |
if (allocated_path == NULL) | |
{ | |
return EXIT_FAILURE; | |
} | |
config->output_path = allocated_path; | |
} | |
else | |
{ | |
if (create_directory_if_not_exists (config->output_path) == -1) | |
{ | |
return EXIT_FAILURE; | |
} | |
} | |
return 0; | |
} | |
/* command_execution.c */ | |
#include "command_execution.h" | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/types.h> | |
#include <sys/wait.h> | |
#include <unistd.h> | |
pid_t | |
fork_process (void) | |
{ | |
pid_t pid = fork (); | |
if (pid == -1) | |
{ | |
perror ("fork"); | |
} | |
return pid; | |
} | |
int | |
setup_pipes (int pipefd[2]) | |
{ | |
if (pipe (pipefd) == -1) | |
{ | |
perror ("pipe"); | |
return -1; | |
} | |
return 0; | |
} | |
int | |
redirect_stdout (int pipefd) | |
{ | |
if (dup2 (pipefd, STDOUT_FILENO) == -1) | |
{ | |
perror ("dup2"); | |
return -1; | |
} | |
return 0; | |
} | |
char * | |
read_from_pipe (int pipefd) | |
{ | |
char buffer[BUFFER_SIZE]; | |
ssize_t bytes_read; | |
size_t output_size = 0; | |
char *output = malloc (1); | |
if (output == NULL) | |
{ | |
perror ("malloc"); | |
return NULL; | |
} | |
output[0] = '\0'; | |
while ((bytes_read = read (pipefd, buffer, BUFFER_SIZE - 1)) > 0) | |
{ | |
buffer[bytes_read] = '\0'; | |
size_t new_size = output_size + bytes_read + 1; | |
char *new_output = realloc (output, new_size); | |
if (new_output == NULL) | |
{ | |
perror ("realloc"); | |
free (output); | |
return NULL; | |
} | |
output = new_output; | |
memcpy (output + output_size, buffer, bytes_read + 1); | |
output_size += bytes_read; | |
} | |
if (bytes_read == -1) | |
{ | |
perror ("read"); | |
free (output); | |
return NULL; | |
} | |
return output; | |
} | |
char * | |
execute_command_with_output (const char *command, char *const argv[]) | |
{ | |
int pipefd[2]; | |
if (setup_pipes (pipefd) == -1) | |
{ | |
return NULL; | |
} | |
pid_t pid = fork_process (); | |
if (pid == -1) | |
{ | |
close (pipefd[READ_END]); | |
close (pipefd[WRITE_END]); | |
return NULL; | |
} | |
else if (pid == 0) | |
{ | |
close (pipefd[READ_END]); | |
if (redirect_stdout (pipefd[WRITE_END]) == -1) | |
{ | |
close (pipefd[1]); | |
exit (EXIT_FAILURE); | |
} | |
close (pipefd[WRITE_END]); | |
execvp (command, argv); | |
perror ("execvp"); | |
exit (EXIT_FAILURE); | |
} | |
else | |
{ | |
close (pipefd[WRITE_END]); | |
char *output = read_from_pipe (pipefd[0]); | |
close (pipefd[READ_END]); | |
int status; | |
if (waitpid (pid, &status, 0) == -1) | |
{ | |
perror ("waitpid"); | |
free (output); | |
return NULL; | |
} | |
if (WIFEXITED (status)) | |
{ | |
int exit_status = WEXITSTATUS (status); | |
if (exit_status != 0) | |
{ | |
fprintf (stderr, "Error: Command exited with status %d\n", exit_status); | |
free (output); | |
return NULL; | |
} | |
} | |
else | |
{ | |
fprintf (stderr, "Error: Child process did not exit normally\n"); | |
free (output); | |
return NULL; | |
} | |
return output; | |
} | |
} | |
int | |
execute_command_without_output (const char *command, char *const argv[]) | |
{ | |
pid_t pid = fork_process (); | |
if (pid == -1) | |
{ | |
return -1; | |
} | |
else if (pid == 0) | |
{ | |
execvp (command, argv); | |
perror ("execvp"); | |
exit (EXIT_FAILURE); | |
} | |
else | |
{ | |
int status; | |
if (waitpid (pid, &status, 0) == -1) | |
{ | |
perror ("waitpid"); | |
return -1; | |
} | |
if (WIFEXITED (status)) | |
{ | |
return WEXITSTATUS (status); | |
} | |
else | |
{ | |
fprintf (stderr, "Error: Child process did not exit normally\n"); | |
return -1; | |
} | |
} | |
} | |
/* directory_management.c */ | |
#include "directory_management.h" | |
#include <errno.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/stat.h> | |
#include <unistd.h> | |
int | |
create_directory_if_not_exists (const char *path) | |
{ | |
struct stat st = {0}; | |
if (stat (path, &st) == -1) | |
{ | |
if (mkdir (path, DIRECTORY_PERMISSIONS) == -1) | |
{ | |
fprintf (stderr, "Error: Failed to create directory %s: %s\n", path, | |
strerror (errno)); | |
return -1; | |
} | |
} | |
return 0; | |
} | |
char * | |
get_current_working_directory (void) | |
{ | |
char *current_dir = getcwd (NULL, 0); | |
if (current_dir == NULL) | |
{ | |
perror ("getcwd"); | |
return NULL; | |
} | |
return current_dir; | |
} | |
/* download_helpers.c */ | |
#include "download_helpers.h" | |
#include "command_execution.h" | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
char ** | |
build_download_command_args (const char *format_code, const char *output_path, | |
const char *url) | |
{ | |
// Calculate the number of arguments | |
// "yt-dlp", "-f", format_code, "-o", output_template, url, NULL | |
int arg_count = 5; | |
if (format_code && strlen (format_code) > 0) | |
{ | |
arg_count = 6; | |
} | |
char **args = malloc (sizeof (char *) * (arg_count + 1)); | |
if (args == NULL) | |
{ | |
fprintf (stderr, "Error: Memory allocation failed for command arguments\n"); | |
return NULL; | |
} | |
args[0] = "yt-dlp"; | |
int idx = 1; | |
if (format_code && strlen (format_code) > 0) | |
{ | |
args[idx++] = "-f"; | |
args[idx++] = (char *) format_code; | |
} | |
else | |
{ | |
args[idx++] = "-f"; | |
args[idx++] = "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best"; | |
} | |
args[idx++] = "-o"; | |
char *output_template = malloc (MAX_PATH_LENGTH); | |
if (output_template == NULL) | |
{ | |
fprintf (stderr, "Error: Memory allocation failed for output template\n"); | |
free (args); | |
return NULL; | |
} | |
snprintf (output_template, MAX_PATH_LENGTH, "%s/%%(title)s.%%(ext)s", | |
output_path); | |
args[idx++] = output_template; | |
args[idx++] = (char *) url; | |
args[idx] = NULL; | |
return args; | |
} | |
void | |
free_command_args (char **args) | |
{ | |
if (args == NULL) | |
return; | |
// The output_template is at index 4 if format_code is provided, else index 3 | |
if (args[4] != NULL) | |
{ | |
free (args[4]); | |
} | |
free (args); | |
} | |
int | |
download_video (const Config *config, const char *format_code) | |
{ | |
printf ("Downloading...\n"); | |
char **args = build_download_command_args (format_code, config->output_path, | |
config->url); | |
if (args == NULL) | |
{ | |
fprintf (stderr, "Error: Failed to build download command arguments\n"); | |
return -1; | |
} | |
int result = execute_command_without_output ("yt-dlp", args); | |
if (result == 0) | |
{ | |
printf ("Download complete! Saved to: %s\n", config->output_path); | |
} | |
else | |
{ | |
fprintf (stderr, "Error: Download failed\n"); | |
} | |
free_command_args (args); | |
return result; | |
} | |
/* format_parsing.c */ | |
#include "format_parsing.h" | |
#include <stdio.h> | |
#include <jansson.h> | |
json_t * | |
parse_formats (const char *json_str) | |
{ | |
json_error_t error; | |
json_t *root = json_loads (json_str, 0, &error); | |
if (!root) | |
{ | |
fprintf (stderr, "Error: JSON parsing failed on line %d: %s\n", error.line, | |
error.text); | |
return NULL; | |
} | |
json_t *formats = json_object_get (root, "formats"); | |
if (!json_is_array (formats)) | |
{ | |
fprintf (stderr, "Error: 'formats' is not an array in JSON data\n"); | |
json_decref (root); | |
return NULL; | |
} | |
// Increment reference count to keep formats alive after root is decref'd | |
json_incref (formats); | |
json_decref (root); | |
return formats; | |
} | |
void | |
display_formats (const json_t *formats) | |
{ | |
printf ("Available formats:\n"); | |
size_t index; | |
json_t *format; | |
json_array_foreach (formats, index, format) | |
{ | |
const char *format_id | |
= json_string_value (json_object_get (format, "format_id")); | |
const char *resolution | |
= json_string_value (json_object_get (format, "resolution")); | |
const char *ext = json_string_value (json_object_get (format, "ext")); | |
json_int_t filesize | |
= json_integer_value (json_object_get (format, "filesize")); | |
printf ("Format code: %-5s Resolution: %-10s Extension: %-4s Filesize: " | |
"%8lld bytes\n", | |
format_id ? format_id : "N/A", resolution ? resolution : "N/A", | |
ext ? ext : "N/A", (long long) filesize); | |
} | |
} | |
/* help_display.c */ | |
#include "help_display.h" | |
#include "directory_management.h" | |
#include <stdio.h> | |
#include <stdlib.h> | |
void | |
display_help (void) | |
{ | |
printf ("Usage: ytdl [OPTION]... URL\n"); | |
printf ("Download videos from YouTube using yt-dlp\n\n"); | |
printf ("Options:\n"); | |
printf (" -h, --help\t\t\tDisplay this help message\n"); | |
printf (" -o, --output PATH\t\tSpecify the output directory (default: " | |
"current directory)\n"); | |
} | |
void | |
cleanup (Config *config) | |
{ | |
if (config->output_path != NULL | |
&& config->output_path != get_current_working_directory ()) | |
{ | |
free ((void *) config->output_path); | |
} | |
} | |
/* main.c */ | |
/** | |
* ytdl.c | |
* | |
* - Tetsuo - c/asm | |
* - https://www.github.com/7etsuo | |
* - https://www.x.com/7etsuo | |
* - https://www.discord.gg/c-asm | |
* | |
* --------------------------------------------------------------------------- | |
* | |
* The author disclaims copyright to this source code. | |
* In place of a legal notice, here is a blessing: | |
* | |
* - May you do good and not evil. | |
* - May you find forgiveness for yourself and forgive others. | |
* - May you share freely, never taking more than you give. | |
* | |
* --------------------------------------------------------------------------- | |
* | |
* Description: | |
* ytdl is a command-line utility for downloading YouTube videos using | |
* yt-dlp. It allows users to specify the output directory& select video | |
* formats. The program fetches video information in JSON format, parses | |
* available formats, and enables users to choose their preferred format | |
* for downloading. | |
* | |
* Features: | |
* - Fetch video metadata using yt-dlp in JSON format. | |
* - Parse and display available video formats with details such as format | |
* code, resolution, extension, and filesize. | |
* - Prompt users to select a desired video format or default to the best | |
* quality available. | |
* - Download the selected video format to a specified or current directory. | |
* | |
* Usage: | |
* ./ytdl [OPTIONS] URL | |
* | |
* Options: | |
* -h, --help Display this help message and exit. | |
* -o, --output PATH Specify the output directory for downloaded videos. | |
* Defaults to the current working directory if not | |
* provided. | |
* | |
* Examples: | |
* - Display help message: | |
* ./ytdl --help | |
* | |
* - Download a video to the current directory: | |
* ./ytdl https://www.youtube.com/watch?v=example | |
* | |
* - Download a video to a specified directory: | |
* ./ytdl -o /path/to/download https://www.youtube.com/watch?v=example | |
* | |
* Dependencies: | |
* - yt-dlp: Ensure that yt-dlp is installed https://github.com/yt-dlp/yt-dlp. | |
* - jansson: A C library for encoding, decoding, and manipulating JSON data. | |
* Installation: | |
* On Debian/Ubuntu: | |
* sudo apt-get install libjansson-dev | |
* On macOS (using Homebrew): | |
* brew install jansson | |
* | |
* Compilation: | |
* To compile the program: make all | |
* | |
* https://www.x.com/7etsuo | |
* --------------------------------------------------------------------------- | |
*/ | |
#include "argument_parsing.h" | |
#include "command_execution.h" | |
#include "directory_management.h" | |
#include "download_helpers.h" | |
#include "format_parsing.h" | |
#include "help_display.h" | |
#include "user_interaction.h" | |
#include "video_info.h" | |
#include "ytdl.h" | |
#include <stdio.h> | |
#include <stdlib.h> | |
int | |
main (int argc, char *argv[]) | |
{ | |
Config config = {0}; | |
int init_result = 0; | |
init_result = parse_arguments (argc, argv, &config); | |
if (init_result != 0) | |
{ | |
cleanup (&config); | |
return init_result; | |
} | |
init_result = initialize_output_path (&config); | |
if (init_result != 0) | |
{ | |
cleanup (&config); | |
return init_result; | |
} | |
printf ("URL: %s\n", config.url); | |
printf ("Output path: %s\n", config.output_path); | |
char *json_str = get_video_info (config.url); | |
if (json_str == NULL) | |
{ | |
fprintf (stderr, "Error: Failed to get video information\n"); | |
cleanup (&config); | |
return EXIT_FAILURE; | |
} | |
json_t *formats = parse_formats (json_str); | |
free (json_str); | |
if (formats == NULL) | |
{ | |
cleanup (&config); | |
return EXIT_FAILURE; | |
} | |
display_formats (formats); | |
json_decref (formats); | |
char *format_code = prompt_for_format (); | |
if (format_code == NULL) | |
{ | |
cleanup (&config); | |
return EXIT_FAILURE; | |
} | |
if (download_video (&config, format_code) != 0) | |
{ | |
fprintf (stderr, "Error: Download failed\n"); | |
free (format_code); | |
cleanup (&config); | |
return EXIT_FAILURE; | |
} | |
free (format_code); | |
cleanup (&config); | |
return EXIT_SUCCESS; | |
} | |
/* m.c */ | |
.text:00575F6E byte_575F6E db 0, 1, 19h, 19h | |
.text:00575F6E ; DATA XREF: _FXCLI_OraBR_Exec_Command+ | |
.text:00575F6F db 19h, 19h, 19h, 19h ; indirect table for switch statement | |
.text:00575F6F db 2, 3, 4, 5 | |
.text:00575F6F db 6, 7, 8, 9 | |
.text:00575F6F db 0Ah, 0Bh, 19h, 19h | |
.text:00575F6F db 19h, 19h, 19h, 19h | |
.text:00575F6F db 0Ch, 0Dh, 0Eh, 0Fh | |
.text:00575F6F db [10h], 11h, 12h, 13h | |
.text:00575F6F db 14h, 19h, 19h, 19h | |
.text:00575F6F db 19h, 19h, 19h, 19h | |
.text:00575F6F db 19h, 19h, 19h, 19h | |
.text:00575F6F db 19h, 19h, 19h, 19h | |
.text:00575F6F db 19h, 19h, 19h, 18h | |
.text:00575F6F db 15h, 16h, 17h, 18h | |
/* user_interaction.c */ | |
#include "user_interaction.h" | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
char * | |
prompt_for_format (void) | |
{ | |
char *format_code = malloc (FORMAT_CODE_LENGTH * sizeof (char)); | |
if (format_code == NULL) | |
{ | |
fprintf (stderr, "Error: Memory allocation failed for format code\n"); | |
return NULL; | |
} | |
printf ("Enter the format code (leave blank for best quality): "); | |
if (fgets (format_code, FORMAT_CODE_LENGTH, stdin) == NULL) | |
{ | |
fprintf (stderr, "Error: Failed to read format code\n"); | |
free (format_code); | |
return NULL; | |
} | |
format_code[strcspn (format_code, "\n")] = 0; | |
return format_code; | |
} | |
/* video_info.c */ | |
#include "video_info.h" | |
#include "command_execution.h" | |
#include <stdio.h> | |
char * | |
get_video_info (const char *url) | |
{ | |
printf ("Fetching video info...\n"); | |
char *const argv[] = {"yt-dlp", "-j", (char *) url, NULL}; | |
return execute_command_with_output ("yt-dlp", argv); | |
} | |
/* argument_parsing.h */ | |
#ifndef ARGUMENT_PARSING_H | |
#define ARGUMENT_PARSING_H | |
#include "ytdl.h" | |
// clang-format off | |
int parse_arguments(int argc, char *argv[], Config *config); | |
int initialize_output_path(Config *config); | |
// clang-format on | |
#endif | |
/* command_execution.h */ | |
#ifndef COMMAND_EXECUTION_H | |
#define COMMAND_EXECUTION_H | |
#include "ytdl.h" | |
// clang-format off | |
pid_t fork_process(void); | |
int setup_pipes(int pipefd[2]); | |
int redirect_stdout(int pipefd); | |
char *read_from_pipe(int pipefd); | |
char *execute_command_with_output(const char *command, char *const argv[]); | |
int execute_command_without_output(const char *command, char *const argv[]); | |
// clang-format on | |
#endif | |
/* directory_management.h */ | |
#ifndef DIRECTORY_MANAGEMENT_H | |
#define DIRECTORY_MANAGEMENT_H | |
#include "ytdl.h" | |
// clang-format off | |
int create_directory_if_not_exists(const char *path); | |
char *get_current_working_directory(void); | |
// clang-format on | |
#endif | |
/* download_helpers.h */ | |
#ifndef DOWNLOAD_HELPERS_H | |
#define DOWNLOAD_HELPERS_H | |
#include "ytdl.h" | |
// clang-format off | |
char **build_download_command_args(const char *format_code, const char *output_path, const char *url); | |
void free_command_args(char **args); | |
int download_video(const Config *config, const char *format_code); | |
// clang-format on | |
#endif | |
/* format_parsing.h */ | |
#ifndef FORMAT_PARSING_H | |
#define FORMAT_PARSING_H | |
#include "ytdl.h" | |
// clang-format off | |
json_t *parse_formats(const char *json_str); | |
void display_formats(const json_t *formats); | |
// clang-format on | |
#endif | |
/* help_display.h */ | |
#ifndef HELP_DISPLAY_H | |
#define HELP_DISPLAY_H | |
#include "ytdl.h" | |
// clang-format off | |
void display_help(void); | |
void cleanup(Config *config); | |
// clang-format on | |
#endif | |
/* user_interaction.h */ | |
#ifndef USER_INTERACTION_H | |
#define USER_INTERACTION_H | |
#include "ytdl.h" | |
// clang-format off | |
char *prompt_for_format(void); | |
// clang-format on | |
#endif | |
/* video_info.h */ | |
#ifndef VIDEO_INFO_H | |
#define VIDEO_INFO_H | |
#include "ytdl.h" | |
// clang-format off | |
char *get_video_info(const char *url); | |
// clang-format on | |
#endif | |
/* ytdl.h */ | |
#ifndef YTDL_H | |
#define YTDL_H | |
#include <errno.h> | |
#include <getopt.h> | |
#include <jansson.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/stat.h> | |
#include <sys/types.h> | |
#include <sys/wait.h> | |
#include <unistd.h> | |
#define MAX_URL_LENGTH 2048 | |
#define MAX_PATH_LENGTH 4096 | |
#define BUFFER_SIZE 1024 | |
#define FORMAT_CODE_LENGTH 20 | |
#define DIRECTORY_PERMISSIONS (S_IRWXU) | |
enum | |
{ | |
READ_END, | |
WRITE_END | |
}; | |
typedef struct | |
{ | |
const char *url; | |
char *output_path; | |
} Config; | |
#endif | |
/* build.sh */ | |
#!/bin/bash | |
# build.sh | |
# This script installs all necessary dependencies for the ytdl program on Debian-based systems. | |
set -e | |
function echo_info { | |
echo -e "\e[34m[INFO]\e[0m $1" | |
} | |
function echo_error { | |
echo -e "\e[31m[ERROR]\e[0m $1" | |
} | |
function command_exists { | |
command -v "$1" >/dev/null 2>&1 | |
} | |
if [[ "$EUID" -ne 0 ]]; then | |
echo_error "Please run this script with sudo or as root." | |
exit 1 | |
fi | |
echo_info "Updating package list..." | |
apt-get update -y | |
echo_info "Installing build-essential..." | |
apt-get install -y build-essential | |
echo_info "Installing libjansson-dev..." | |
apt-get install -y libjansson-dev | |
echo_info "Installing pkg-config..." | |
apt-get install -y pkg-config | |
echo_info "Installing libssl-dev..." | |
apt-get install -y libssl-dev | |
echo_info "Installing libcurl4-openssl-dev..." | |
apt-get install -y libcurl4-openssl-dev | |
echo_info "Installing python3-pip..." | |
apt-get install -y python3-pip | |
echo_info "All system dependencies installed successfully." | |
echo | |
echo "--------------------------------------------------" | |
echo "Next Steps:" | |
echo "1. Install yt-dlp using pip3:" | |
echo " pip3 install yt-dlp" | |
echo | |
echo "2. Compile the ytdl program:" | |
echo " make all" | |
echo | |
echo "3. Run the program:" | |
echo " ./ytdl [OPTIONS] URL" | |
echo "--------------------------------------------------" | |
echo | |
/* log.sh */ | |
#!/bin/bash | |
rm -f log.c | |
for file in *.c *.h *.sh; do | |
if [ -f "$file" ]; then | |
echo "/* $file */" >> log.c | |
cat "$file" >> log.c | |
echo "" >> log.c | |
fi | |
done | |
echo "All .c, .h, and .sh files have been concatenated into log.c" | |
/* Makefile */ | |
CC = gcc | |
CFLAGS = -Wall -Wextra -pedantic -std=c11 | |
LDFLAGS = -ljansson | |
SRCS = main.c command_execution.c video_info.c format_parsing.c user_interaction.c directory_management.c download_helpers.c argument_parsing.c help_display.c | |
OBJS = $(SRCS:.c=.o) | |
TARGET = ytdl | |
.PHONY: all clean | |
all: $(TARGET) | |
$(TARGET): $(OBJS) | |
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) | |
%.o: %.c | |
$(CC) $(CFLAGS) -c $< | |
clean: | |
rm -f $(OBJS) $(TARGET) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment