2021-10-30 21:24:15 -07:00
//! Mach system SDK inclusion
//!
//! This file contains all that you need to include the Mach system SDKs in your own build.zig,
//! allowing you to cross-compile most OpenGL/Vulkan applications with ease.
//!
//! The SDKs used by this script by default are:
//!
2022-03-04 16:35:43 -07:00
//! * Windows: https://github.com/hexops/sdk-windows-x86_64 (~7MB, updated DirectX headers for Zig/MinGW)
2021-10-30 21:24:15 -07:00
//! * Linux: https://github.com/hexops/sdk-linux-x86_64 (~40MB, X11, Wayland, etc. development libraries)
2021-11-26 23:25:50 -07:00
//! * MacOS (most frameworks you'd find in the XCode SDK):
//! * https://github.com/hexops/sdk-macos-11.3 (~160MB, default)
//! * https://github.com/hexops/sdk-macos-12.0 (~112MB, only if you specify a macOS 12 target triple.)
2021-10-30 21:24:15 -07:00
//!
//! You may supply your own SDKs via the Options struct if needed, although the Mach versions above
//! will generally work for most OpenGL/Vulkan applications.
//!
//! How it works: When `include` is called, the compilation target is detected. If it does not
//! already exist, the SDK repository for the target platform is cloned via `git clone`. If the
//! target is MacOS, an interactive license agreement prompt (agreeing to the XCode SDK terms)
//! will appear. You can also set the environment variable `AGREE=true` to dismiss this.
//!
//! Once downloaded, `include` will add the SDK library, header, etc. directions to the build step
//! so that you can just include and link against libraries/frameworks as if they were there, and
//! you may then cross-compile your code with ease. See https://github.com/hexops/mach-glfw for an
//! example.
//!
//! Best way to get this file in your own repository? We suggest just copying it, or importing it
//! from a project that includes it if you're using one (e.g. mach-glfw)
//!
2022-03-04 16:35:43 -07:00
//! version: Mar 4, 2022
2021-10-30 21:24:15 -07:00
const std = @import ( " std " ) ;
2023-02-07 19:19:47 +01:00
const Build = std . Build ;
2021-10-30 21:24:15 -07:00
pub const Options = struct {
2022-10-26 18:29:26 +03:30
pub const Sdk = struct {
name : [ ] const u8 ,
git_addr : [ ] const u8 ,
git_revision : [ ] const u8 ,
cpu_arch : [ ] const std . Target . Cpu . Arch ,
os_tag : std . Target . Os . Tag ,
os_version : std . Target . Os . TaggedVersionRange ,
} ;
2022-03-30 10:03:00 -07:00
2022-10-26 18:29:26 +03:30
sdk_list : [ ] const Sdk = & . {
. {
. name = " sdk-macos-12.0 " ,
. git_addr = " https://github.com/hexops/sdk-macos-12.0 " ,
. git_revision = " 14613b4917c7059dad8f3789f55bb13a2548f83d " ,
. cpu_arch = & . { . aarch64 , . x86_64 } ,
. os_tag = . macos ,
. os_version = . {
. semver = . {
. min = . { . major = 12 , . minor = 0 } ,
2022-11-25 10:31:04 -07:00
. max = . { . major = 13 , . minor = std . math . maxInt ( u32 ) } ,
2022-10-26 18:29:26 +03:30
} ,
} ,
} ,
. {
. name = " sdk-macos-11.3 " ,
. git_addr = " https://github.com/hexops/sdk-macos-11.3 " ,
. git_revision = " ccbaae84cc39469a6792108b24480a4806e09d59 " ,
. cpu_arch = & . { . aarch64 , . x86_64 } ,
. os_tag = . macos ,
. os_version = . {
. semver = . {
. min = . { . major = 11 , . minor = 3 } ,
. max = . { . major = 11 , . minor = std . math . maxInt ( u32 ) } ,
} ,
} ,
} ,
. {
. name = " sdk-linux-x86_64 " ,
. git_addr = " https://github.com/hexops/sdk-linux-x86_64 " ,
2023-01-21 01:09:47 +03:30
. git_revision = " 4a885f866b1d66208be4c3208c7d7fea092742b4 " ,
2022-10-26 18:29:26 +03:30
. cpu_arch = & . { . x86_64 } ,
. os_tag = . linux ,
. os_version = . {
. linux = . {
. range = . {
. min = . { . major = 3 , . minor = 16 } ,
2022-10-29 17:50:13 +00:00
. max = . { . major = 6 , . minor = std . math . maxInt ( u32 ) } ,
2022-10-26 18:29:26 +03:30
} ,
. glibc = . { . major = 0 , . minor = 0 } ,
} ,
} ,
} ,
. {
. name = " sdk-linux-aarch64 " ,
. git_addr = " https://github.com/hexops/sdk-linux-aarch64 " ,
2023-01-21 01:09:47 +03:30
. git_revision = " f87c90c2e598b0d596a01e67b5e2172bc7753ce6 " ,
2022-10-26 18:29:26 +03:30
. cpu_arch = & . { . aarch64 } ,
. os_tag = . linux ,
. os_version = . {
. linux = . {
. range = . {
. min = . { . major = 3 , . minor = 16 } ,
2022-10-29 17:50:13 +00:00
. max = . { . major = 6 , . minor = std . math . maxInt ( u32 ) } ,
2022-10-26 18:29:26 +03:30
} ,
. glibc = . { . major = 0 , . minor = 0 } ,
} ,
} ,
} ,
. {
. name = " sdk-windows-x86_64 " ,
. git_addr = " https://github.com/hexops/sdk-windows-x86_64 " ,
. git_revision = " 13dcda7fe3f1aec0fc6130527226ad7ae0f4b792 " ,
. cpu_arch = & . { . x86_64 } ,
. os_tag = . windows ,
. os_version = . {
. windows = . {
. min = . win7 ,
. max = std . Target . Os . WindowsVersion . latest ,
} ,
} ,
} ,
} ,
2022-03-04 15:38:53 -07:00
2021-10-30 21:24:15 -07:00
/// If true, the Builder.sysroot will set to the SDK path. This has the drawback of preventing
/// you from including headers, libraries, etc. from outside the SDK generally. However, it can
/// be useful in order to identify which libraries, headers, frameworks, etc. may be missing in
/// your SDK for cross compilation.
set_sysroot : bool = false ,
} ;
2023-02-07 19:19:47 +01:00
pub fn include ( b : * Build , step : * std . build . CompileStep , options : Options ) void {
2022-10-26 18:29:26 +03:30
const target = step . target_info . target ;
// var best_sdk: ?Options.Sdk = null;
for ( options . sdk_list ) | sdk | {
if ( ! std . mem . containsAtLeast ( std . Target . Cpu . Arch , sdk . cpu_arch , 1 , & . { target . cpu . arch } ) )
continue ;
if ( sdk . os_tag ! = target . os . tag ) continue ;
const version_ok = switch ( sdk . os_version ) {
. semver = > | vr | vr . includesVersion ( target . os . version_range . semver . min ) or vr . includesVersion ( target . os . version_range . semver . max ) ,
. linux = > | vr | vr . includesVersion ( target . os . version_range . linux . range . min ) or vr . includesVersion ( target . os . version_range . linux . range . max ) ,
. windows = > | vr | vr . includesVersion ( target . os . version_range . windows . min ) or vr . includesVersion ( target . os . version_range . windows . max ) ,
. none = > false ,
} ;
if ( ! version_ok ) continue ;
const sdk_root_dir = getSdkRoot ( b . allocator , sdk ) catch unreachable ;
if ( options . set_sysroot ) {
// We have no sysroot for Windows, but we still set one to prevent inclusion of other system
// libs (if set_sysroot is set, don't want to accidentally depend on system libs.)
b . sysroot = sdk_root_dir ;
}
return switch ( target . os . tag ) {
. windows = > {
const sdk_includes = std . fs . path . join ( b . allocator , & . { sdk_root_dir , " include " } ) catch unreachable ;
const sdk_libs = std . fs . path . join ( b . allocator , & . { sdk_root_dir , " lib " } ) catch unreachable ;
defer {
b . allocator . free ( sdk_includes ) ;
b . allocator . free ( sdk_libs ) ;
}
step . addIncludePath ( sdk_includes ) ;
step . addLibraryPath ( sdk_libs ) ;
} ,
. macos = > {
if ( options . set_sysroot ) {
step . addFrameworkPath ( " /System/Library/Frameworks " ) ;
step . addSystemIncludePath ( " /usr/include " ) ;
step . addLibraryPath ( " /usr/lib " ) ;
return ;
}
const sdk_framework_dir = std . fs . path . join ( b . allocator , & . { sdk_root_dir , " root/System/Library/Frameworks " } ) catch unreachable ;
const sdk_include_dir = std . fs . path . join ( b . allocator , & . { sdk_root_dir , " root/usr/include " } ) catch unreachable ;
const sdk_lib_dir = std . fs . path . join ( b . allocator , & . { sdk_root_dir , " root/usr/lib " } ) catch unreachable ;
defer {
b . allocator . free ( sdk_framework_dir ) ;
b . allocator . free ( sdk_include_dir ) ;
b . allocator . free ( sdk_lib_dir ) ;
}
step . addFrameworkPath ( sdk_framework_dir ) ;
step . addSystemIncludePath ( sdk_include_dir ) ;
step . addLibraryPath ( sdk_lib_dir ) ;
} ,
. linux = > {
if ( options . set_sysroot ) return ;
const sdk_root_includes = std . fs . path . join ( b . allocator , & . { sdk_root_dir , " root/usr/include " } ) catch unreachable ;
const wayland_protocols_include = std . fs . path . join ( b . allocator , & . { sdk_root_dir , " root/usr/share/wayland-generated " } ) catch unreachable ;
const sdk_root_libs = switch ( target . cpu . arch ) {
. x86_64 = > std . fs . path . join ( b . allocator , & . { sdk_root_dir , " root/usr/lib/x86_64-linux-gnu " } ) catch unreachable ,
. aarch64 = > std . fs . path . join ( b . allocator , & . { sdk_root_dir , " root/usr/lib/aarch64-linux-gnu " } ) catch unreachable ,
else = > break ,
} ;
defer {
b . allocator . free ( sdk_root_includes ) ;
b . allocator . free ( wayland_protocols_include ) ;
b . allocator . free ( sdk_root_libs ) ;
}
step . addSystemIncludePath ( sdk_root_includes ) ;
step . addSystemIncludePath ( wayland_protocols_include ) ;
step . addLibraryPath ( sdk_root_libs ) ;
} ,
else = > unreachable ,
} ;
2022-03-04 15:38:53 -07:00
}
2022-10-26 18:29:26 +03:30
// TODO(error_handling)
std . debug . print ( " Unsupported Target! \n " , . { } ) ;
unreachable ;
2022-03-04 15:38:53 -07:00
}
2022-10-26 18:29:26 +03:30
var cached_sdk_roots : ? std . AutoHashMap ( * const Options . Sdk , [ ] const u8 ) = null ;
2021-12-10 03:33:31 -07:00
/// returns the SDK root path, determining it iff necessary. In a real application, this may be
/// tens or hundreds of times and so the result is cached in-memory (this also means the result
/// cannot be freed until the result will never be used again, which is fine as the Zig build system
/// Builder.allocator is an arena, you don't need to free.)
2022-10-26 18:29:26 +03:30
fn getSdkRoot ( allocator : std . mem . Allocator , sdk : Options . Sdk ) ! [ ] const u8 {
if ( cached_sdk_roots = = null )
cached_sdk_roots = std . AutoHashMap ( * const Options . Sdk , [ ] const u8 ) . init ( allocator ) ;
2022-07-22 19:30:59 -07:00
2022-10-26 18:29:26 +03:30
var entry = try cached_sdk_roots . ? . getOrPut ( & sdk ) ;
2022-07-22 19:30:59 -07:00
if ( entry . found_existing ) return entry . value_ptr . * ;
2022-10-26 18:29:26 +03:30
const sdk_root = try determineSdkRoot ( allocator , sdk ) ;
2022-07-22 19:30:59 -07:00
entry . value_ptr . * = sdk_root ;
return sdk_root ;
2021-12-10 03:33:31 -07:00
}
2022-10-26 18:29:26 +03:30
fn determineSdkRoot ( allocator : std . mem . Allocator , sdk : Options . Sdk ) ! [ ] const u8 {
2021-10-30 21:24:15 -07:00
// Find the directory where the SDK should be located. We'll consider two locations:
//
// 1. $SDK_PATH/<name> (if set, e.g. for testing changes to SDKs easily)
// 2. <appdata>/<name> (default)
//
2021-11-21 11:09:35 -07:00
// Where `<name>` is the name of the SDK, e.g. `sdk-macos-12.0`.
2021-10-30 21:24:15 -07:00
var sdk_root_dir : [ ] const u8 = undefined ;
var sdk_path_dir : [ ] const u8 = undefined ;
2021-11-28 20:19:23 -07:00
var custom_sdk_path = false ;
2022-10-09 19:55:46 +03:30
if ( std . process . getEnvVarOwned ( allocator , " MACH_SDK_PATH " ) ) | sdk_path | {
2021-11-28 20:19:23 -07:00
custom_sdk_path = true ;
2021-10-30 21:24:15 -07:00
sdk_path_dir = sdk_path ;
2022-10-26 18:29:26 +03:30
sdk_root_dir = try std . fs . path . join ( allocator , & . { sdk_path , sdk . name } ) ;
2021-10-30 21:24:15 -07:00
} else | err | switch ( err ) {
error . EnvironmentVariableNotFound = > {
2022-10-26 18:29:26 +03:30
sdk_path_dir = try std . fs . getAppDataDir ( allocator , " mach " ) ;
sdk_root_dir = try std . fs . path . join ( allocator , & . { sdk_path_dir , sdk . name } ) ;
2021-10-30 21:24:15 -07:00
} ,
else = > | e | return e ,
}
2022-05-29 19:39:31 +07:00
ensureGit ( allocator ) ;
2021-10-30 21:24:15 -07:00
// If the SDK exists, return it. Otherwise, clone it.
2022-10-14 17:55:15 +02:00
if ( std . fs . openDirAbsolute ( sdk_root_dir , . { } ) ) | _ | {
2021-12-10 03:33:31 -07:00
const current_revision = try getCurrentGitRevision ( allocator , sdk_root_dir ) ;
2022-10-26 18:29:26 +03:30
if ( ! std . mem . eql ( u8 , current_revision , sdk . git_revision ) ) {
2021-12-10 03:33:31 -07:00
// Update the SDK to the target revision. This may be either forward or backwards in
// history (e.g. if building an old project) and so we use a hard reset.
//
// No reset is performed if specifying a custom SDK_PATH, as that is a development/debug
// option and could wipe out dev history.
2022-10-26 18:29:26 +03:30
exec ( allocator , & [ _ ] [ ] const u8 { " git " , " fetch " } , sdk_root_dir ) catch | err | std . debug . print ( " warning: failed to check for updates to {s}: {s} \n " , . { sdk . name , @errorName ( err ) } ) ;
if ( ! custom_sdk_path )
try exec ( allocator , & [ _ ] [ ] const u8 { " git " , " reset " , " --quiet " , " --hard " , sdk . git_revision } , sdk_root_dir ) ;
2021-12-10 03:33:31 -07:00
}
2021-10-30 21:24:15 -07:00
return sdk_root_dir ;
} else | err | return switch ( err ) {
error . FileNotFound = > {
2022-10-26 18:29:26 +03:30
std . log . info ( " cloning required sdk.. \n git clone {s} '{s}'.. \n " , . { sdk . git_addr , sdk_root_dir } ) ;
switch ( sdk . os_tag ) {
. macos , . ios , . watchos , . tvos = > {
if ( ! try confirmAppleSDKAgreement ( allocator ) ) @panic ( " cannot continue " ) ;
} ,
else = > { } ,
2021-10-30 21:24:15 -07:00
}
try std . fs . cwd ( ) . makePath ( sdk_path_dir ) ;
2022-10-26 18:29:26 +03:30
try exec ( allocator , & [ _ ] [ ] const u8 { " git " , " clone " , " -c " , " core.longpaths=true " , sdk . git_addr } , sdk_path_dir ) ;
2021-10-30 21:24:15 -07:00
return sdk_root_dir ;
} ,
else = > err ,
} ;
}
2021-12-06 17:35:47 +06:00
fn exec ( allocator : std . mem . Allocator , argv : [ ] const [ ] const u8 , cwd : [ ] const u8 ) ! void {
2022-05-01 19:44:27 +01:00
var child = std . ChildProcess . init ( argv , allocator ) ;
2021-11-28 18:11:43 -07:00
child . cwd = cwd ;
2021-12-10 03:33:31 -07:00
_ = try child . spawnAndWait ( ) ;
}
fn getCurrentGitRevision ( allocator : std . mem . Allocator , cwd : [ ] const u8 ) ! [ ] const u8 {
const result = try std . ChildProcess . exec ( . { . allocator = allocator , . argv = & . { " git " , " rev-parse " , " HEAD " } , . cwd = cwd } ) ;
allocator . free ( result . stderr ) ;
if ( result . stdout . len > 0 ) return result . stdout [ 0 . . result . stdout . len - 1 ] ; // trim newline
return result . stdout ;
2021-11-28 18:11:43 -07:00
}
2021-12-06 17:35:47 +06:00
fn confirmAppleSDKAgreement ( allocator : std . mem . Allocator ) ! bool {
2021-10-30 21:24:15 -07:00
if ( std . process . getEnvVarOwned ( allocator , " AGREE " ) ) | agree | {
return std . mem . eql ( u8 , agree , " true " ) ;
} else | err | switch ( err ) {
error . EnvironmentVariableNotFound = > { } ,
else = > | e | return e ,
}
const stdin = std . io . getStdIn ( ) . reader ( ) ;
const stdout = std . io . getStdOut ( ) . writer ( ) ;
var buf : [ 10 ] u8 = undefined ;
try stdout . print ( " This SDK is distributed under the terms of the Xcode and Apple SDKs agreement: \n " , . { } ) ;
try stdout . print ( " https://www.apple.com/legal/sla/docs/xcode.pdf \n " , . { } ) ;
try stdout . print ( " \n " , . { } ) ;
try stdout . print ( " Do you agree to those terms? [Y/n] " , . { } ) ;
if ( try stdin . readUntilDelimiterOrEof ( buf [ 0 . . ] , '\n' ) ) | user_input | {
try stdout . print ( " \n " , . { } ) ;
var in = user_input ;
if ( in . len > 0 and in [ in . len - 1 ] = = '\r' ) in = in [ 0 . . in . len - 1 ] ;
return std . mem . eql ( u8 , in , " y " ) or std . mem . eql ( u8 , in , " Y " ) or std . mem . eql ( u8 , in , " yes " ) or std . mem . eql ( u8 , in , " " ) ;
} else {
return false ;
}
}
2022-05-27 10:27:05 +07:00
fn ensureGit ( allocator : std . mem . Allocator ) void {
const argv = & [ _ ] [ ] const u8 { " git " , " --version " } ;
const result = std . ChildProcess . exec ( . {
. allocator = allocator ,
. argv = argv ,
. cwd = " . " ,
} ) catch { // e.g. FileNotFound
std . log . err ( " mach: error: 'git --version' failed. Is git not installed? " , . { } ) ;
std . process . exit ( 1 ) ;
} ;
defer {
allocator . free ( result . stderr ) ;
allocator . free ( result . stdout ) ;
}
if ( result . term . Exited ! = 0 ) {
std . log . err ( " mach: error: 'git --version' failed. Is git not installed? " , . { } ) ;
std . process . exit ( 1 ) ;
}
}