Skip to content

Commit 6a10b46

Browse files
committed
glad: add option to make glad builds reproducible.
gh: Dav1dde#162
1 parent ddf83fb commit 6a10b46

19 files changed

+67010
-31
lines changed

.gitignore

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
*.pyc
2-
*.xml
2+
/*.xml
33
*.kdev4
44
build/
55
*.o
@@ -11,8 +11,9 @@ main.c
1111
.idea
1212
dist/
1313
*.egg-info
14-
khrplatform.h
15-
eglplatform.h
14+
/khrplatform.h
15+
/eglplatform.h
16+
/vk_platform.h
1617
/glad-rs/
1718
/rust/
1819
target/

MANIFEST.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
recursive-include glad *.c *.h *.d *.volt *.rs *.toml
1+
recursive-include glad *.c *.h *.d *.volt *.rs *.toml *.xml

cmake/CMakeLists.txt

+5-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ endfunction()
174174

175175
# Create a glad library named "${TARGET}"
176176
function(glad_add_library TARGET)
177-
cmake_parse_arguments(GG "MERGE;QUIET;KEEP_SOURCES;STATIC;SHARED;MODULE;EXCLUDE_FROM_ALL" "LOCATION;LANGUAGE" "API;EXTENSIONS" ${ARGN})
177+
cmake_parse_arguments(GG "MERGE;QUIET;REPRODUCIBLE;KEEP_SOURCES;STATIC;SHARED;MODULE;EXCLUDE_FROM_ALL" "LOCATION;LANGUAGE" "API;EXTENSIONS" ${ARGN})
178178

179179
if(NOT GG_LOCATION)
180180
set(GG_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/gladsources/${TARGET}")
@@ -209,6 +209,10 @@ function(glad_add_library TARGET)
209209
list(APPEND GLAD_ARGS --merge)
210210
endif()
211211

212+
if(GG_REPRODUCIBLE)
213+
list(APPEND GLAD_ARGS --reproducible)
214+
endif()
215+
212216
set(GLAD_LANGUAGE "c")
213217
if(GG_LANGUAGE)
214218
string(TOLOWER "${GG_LANGUAGE}" "${GLAD_LANGUAGE}")

example/c++/multiwin_mx/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ find_package(glfw3 REQUIRED)
66
set(GLAD_SOURCES_DIR "${PROJECT_SOURCE_DIR}/../../..")
77
add_subdirectory("${GLAD_SOURCES_DIR}/cmake" glad_cmake)
88

9-
glad_add_library(glad_gl_core_mx_33 MX API gl:core=3.3)
9+
glad_add_library(glad_gl_core_mx_33 REPRODUCIBLE MX API gl:core=3.3)
1010

1111
add_executable(multiwin_mx
1212
multiwin_mx.cpp

example/c/egl_x11/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ find_package(X11 REQUIRED)
66
set(GLAD_SOURCES_DIR "${PROJECT_SOURCE_DIR}/../../..")
77
add_subdirectory("${GLAD_SOURCES_DIR}/cmake" glad_cmake)
88

9-
glad_add_library(glad_egl_15_gles2_20 LOADER API egl=1.5 gles2=2.0)
9+
glad_add_library(glad_egl_15_gles2_20 REPRODUCIBLE LOADER API egl=1.5 gles2=2.0)
1010
add_executable(egl_x11
1111
egl_x11.c
1212
)

example/c/vulkan_tri_glfw/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ add_subdirectory("${GLAD_SOURCES_DIR}/cmake" glad_cmake)
66

77
find_package(glfw3 REQUIRED)
88

9-
glad_add_library(glad_vulkan_11 LOADER API vulkan=1.1)
9+
glad_add_library(glad_vulkan_11 REPRODUCIBLE LOADER API vulkan=1.1)
1010
add_executable(vulkan_tri_glfw
1111
vulkan_tri_glfw.c
1212
)

glad/__main__.py

+20-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import logging
1010
import os
1111

12+
import glad.files
1213
from glad.config import Config, ConfigOption
1314
from glad.sink import LoggingSink
1415
from glad.opener import URLOpener
@@ -48,7 +49,7 @@ class GlobalConfig(Config):
4849
default=None,
4950
description='Path to a file containing a list of extensions or '
5051
'a comma separated list of extensions, if missing '
51-
'all possible extensions are included'
52+
'all possible extensions are included.'
5253
)
5354
MERGE = ConfigOption(
5455
converter=bool,
@@ -57,11 +58,17 @@ class GlobalConfig(Config):
5758
)
5859
QUIET = ConfigOption(
5960
converter=bool,
60-
description='Disable logging'
61+
description='Disable logging.'
62+
)
63+
REPRODUCIBLE = ConfigOption(
64+
converter=bool,
65+
default=False,
66+
description='Makes the build reproducible by not fetching the latest '
67+
'specification from Khronos.'
6168
)
6269

6370

64-
def load_specifications(specification_names, opener, specification_classes=None):
71+
def load_specifications(specification_names, opener, specification_classes=None, reproducible=False):
6572
specifications = dict()
6673

6774
if specification_classes is None:
@@ -71,7 +78,10 @@ def load_specifications(specification_names, opener, specification_classes=None)
7178
Specification = specification_classes[name]
7279
xml_name = name + '.xml'
7380

74-
if os.path.isfile(xml_name):
81+
if reproducible and False:
82+
logger.info('reproducible build, using packaged specification: %s', xml_name)
83+
specification = Specification.from_file(glad.files.open_local(xml_name))
84+
elif os.path.isfile(xml_name):
7585
logger.info('using local specification: %s', xml_name)
7686
specification = Specification.from_file(xml_name, opener=opener)
7787
else:
@@ -132,11 +142,15 @@ def main(args=None):
132142
global_config.validate() # Done before, but doesn't hurt
133143
config.validate()
134144

135-
opener = URLOpener()
145+
if global_config['REPRODUCIBLE']:
146+
opener = glad.files.StaticFileOpener()
147+
else:
148+
opener = URLOpener()
136149

137150
specifications = load_specifications(
138151
[value[0] for value in global_config['API'].values()],
139-
opener=opener
152+
opener=opener,
153+
reproducible=global_config['REPRODUCIBLE']
140154
)
141155

142156
generator = generators[ns.subparser_name](global_config['OUT_PATH'], opener=opener)

glad/files/__init__.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import os.path
2+
import logging
3+
import shutil
4+
5+
try:
6+
from urlparse import urlparse
7+
except ImportError:
8+
from urllib.parse import urlparse
9+
10+
try:
11+
from pkg_resources import resource_exists, resource_stream
12+
except ImportError:
13+
def resource_exists(*args, **kwargs):
14+
return False
15+
16+
def resource_stream(*args, **kwargs):
17+
return None
18+
19+
20+
BASE_PATH = os.path.abspath(os.path.dirname(__file__))
21+
22+
logger = logging.getLogger('glad.files')
23+
24+
25+
class GladFileException(Exception):
26+
pass
27+
28+
29+
def open_local(name, *args, **kwargs):
30+
# use pkg_resources when available, makes it work in zipped modules
31+
# or other environments
32+
if resource_exists(__name__, name):
33+
logger.info('opening packaged resource: %r', name)
34+
return resource_stream(__name__, name)
35+
36+
# fallback to filesystem
37+
logger.info('opening packaged path: %r', name)
38+
local_path = os.path.normpath(os.path.join(BASE_PATH, os.path.join(name)))
39+
if not local_path.startswith(BASE_PATH):
40+
raise GladFileException('unsafe file path, won\'t open {!r}'.format(local_path))
41+
return open(local_path, *args, **kwargs)
42+
43+
44+
class StaticFileOpener(object):
45+
def urlopen(self, url, data=None, *args, **kwargs):
46+
logger.debug('intercepted attempt to retrieve remote resource %r', url)
47+
if data is not None:
48+
raise GladFileException('can not resolve requests with payload')
49+
50+
filename = urlparse(url).path.rsplit('/', 1)[-1]
51+
return open_local(filename, 'rb')
52+
53+
def urlretrieve(self, url, filename, *args, **kwargs):
54+
with self.urlopen(url) as src:
55+
with open(filename, 'wb') as dst:
56+
shutil.copyfileobj(src, dst)

0 commit comments

Comments
 (0)