Repository: brendanzab/gl-rs (language: rust)

Issue:
Output not deterministic
I was investigating why sccache was not caching some parts of webrender (which @jrmuizel noticed) and I tracked it down to `gleam` using `gl_generator` to generate some source files, and those source files not being generated in a deterministic manner. [Here's a diff](https://gist.github.com/luser/354d77d2e202b93456c50e67cfdac36a) between two runs.

[gleam's build script](https://github.com/servo/gleam/blob/master/build.rs) doesn't seem to be doing anything unusual.

If I had to guess, I would guess it's due to [`Registry` using `HashSet` / `HashMap`](https://github.com/brendanzab/gl-rs/blob/d8c58776e9908d645ff651cfa27bfa2e20c8c954/gl_generator/registry/mod.rs#L94). Iterating over those items will not use a deterministic ordering. It should be simple enough to change those to `BTreeSet` / `BTreeMap` to fix that.


Discussion / hints:

Fix patch:
diff --git a/gl_generator/registry/mod.rs b/gl_generator/registry/mod.rs
index 497a877d..764f2502 100644
--- a/gl_generator/registry/mod.rs
+++ b/gl_generator/registry/mod.rs
@@ -15,7 +15,7 @@
 extern crate khronos_api;
 
 use std::borrow::Cow;
-use std::collections::{BTreeSet, HashMap, HashSet};
+use std::collections::{BTreeMap, BTreeSet};
 use std::fmt;
 use std::hash::{Hash, Hasher};
 use std::io;
@@ -48,7 +48,7 @@ pub enum Fallbacks { All, None }
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub enum Profile { Core, Compatibility }
 
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub struct Enum {
     pub ident: String,
     pub value: String,
@@ -63,13 +63,13 @@ impl Hash for Enum {
     }
 }
 
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub struct Binding {
     pub ident: String,
     pub ty: Cow<'static, str>,
 }
 
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub struct Cmd {
     pub proto: Binding,
     pub params: Vec<Binding>,
@@ -84,7 +84,7 @@ impl Hash for Cmd {
     }
 }
 
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub struct GlxOpcode {
     pub opcode: String,
     pub name: Option<String>,
@@ -93,9 +93,9 @@ pub struct GlxOpcode {
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct Registry {
     pub api: Api,
-    pub enums: HashSet<Enum>,
-    pub cmds: HashSet<Cmd>,
-    pub aliases: HashMap<String, Vec<String>>,
+    pub enums: BTreeSet<Enum>,
+    pub cmds: BTreeSet<Cmd>,
+    pub aliases: BTreeMap<String, Vec<String>>,
 }
 
 impl Registry {
diff --git a/gl_generator/registry/parse.rs b/gl_generator/registry/parse.rs
index 570c9cee..4db77b11 100644
--- a/gl_generator/registry/parse.rs
+++ b/gl_generator/registry/parse.rs
@@ -15,10 +15,8 @@
 extern crate khronos_api;
 
 use std::borrow::Cow;
-use std::collections::hash_map::Entry;
-use std::collections::BTreeSet;
-use std::collections::HashSet;
-use std::collections::HashMap;
+use std::collections::btree_map::Entry;
+use std::collections::{BTreeMap, BTreeSet};
 use std::io;
 use xml::attribute::OwnedAttribute;
 use xml::EventReader as XmlEventReader;
@@ -184,7 +182,7 @@ fn trim_cmd_prefix(ident: &str, api: Api) -> &str {
     }
 }
 
-fn merge_map(a: &mut HashMap<String, Vec<String>>, b: HashMap<String, Vec<String>>) {
+fn merge_map(a: &mut BTreeMap<String, Vec<String>>, b: BTreeMap<String, Vec<String>>) {
     for (k, v) in b {
         match a.entry(k) {
             Entry::Occupied(mut ent) => { ent.get_mut().extend(v); },
@@ -244,7 +242,7 @@ trait Parse: Sized + Iterator<Item = ParseEvent> {
         let mut cmds = Vec::new();
         let mut features = Vec::new();
         let mut extensions = Vec::new();
-        let mut aliases = HashMap::new();
+        let mut aliases = BTreeMap::new();
 
         while let Some(event) = self.next() {
             match event {
@@ -291,8 +289,8 @@ trait Parse: Sized + Iterator<Item = ParseEvent> {
             }
         }
 
-        let mut desired_enums = HashSet::new();
-        let mut desired_cmds = HashSet::new();
+        let mut desired_enums = BTreeSet::new();
+        let mut desired_cmds = BTreeSet::new();
 
         // find the features we want
         let mut found_feature = false;
@@ -356,7 +354,7 @@ trait Parse: Sized + Iterator<Item = ParseEvent> {
             api: filter.api,
             enums: enums.into_iter().filter(is_desired_enum).collect(),
             cmds: cmds.into_iter().filter(is_desired_cmd).collect(),
-            aliases: if filter.fallbacks == Fallbacks::None { HashMap::new() } else { aliases },
+            aliases: if filter.fallbacks == Fallbacks::None { BTreeMap::new() } else { aliases },
         }
     }
 
@@ -475,9 +473,9 @@ trait Parse: Sized + Iterator<Item = ParseEvent> {
         make_enum(ident, ty, value, alias)
     }
 
-    fn consume_cmds(&mut self, api: Api) -> (Vec<Cmd>, HashMap<String, Vec<String>>) {
+    fn consume_cmds(&mut self, api: Api) -> (Vec<Cmd>, BTreeMap<String, Vec<String>>) {
         let mut cmds = Vec::new();
-        let mut aliases: HashMap<String, Vec<String>> = HashMap::new();
+        let mut aliases: BTreeMap<String, Vec<String>> = BTreeMap::new();
         loop {
             match self.next().unwrap() {
                 // add command definition


Test patch: