summaryrefslogtreecommitdiff
path: root/2024/09
diff options
context:
space:
mode:
Diffstat (limited to '2024/09')
-rw-r--r--2024/09/12/Functional-Reactive-Programming-in-F/index.html37
-rw-r--r--2024/09/15/Advanced-C-binding-using-ocaml-ctypes-and-dune/index.html56
-rw-r--r--2024/09/15/肩周炎和肩袖损伤的区别/index.html15
-rw-r--r--2024/09/27/Turborepo-简述/index.html129
4 files changed, 112 insertions, 125 deletions
diff --git a/2024/09/12/Functional-Reactive-Programming-in-F/index.html b/2024/09/12/Functional-Reactive-Programming-in-F/index.html
index 94586aea..b1bc57f8 100644
--- a/2024/09/12/Functional-Reactive-Programming-in-F/index.html
+++ b/2024/09/12/Functional-Reactive-Programming-in-F/index.html
@@ -209,7 +209,8 @@
<p>We saw in the previous post on message queues that one of the advantages of that approach was that the requests were “serialized” making it conceptually easier to deal with.</p>
<p>There is a similar approach that can be used with events. The idea is to turn a series of events into an “event stream”. Event streams then become quite like IEnumerables, and so the obvious next step is to treat them in much the the same way that LINQ handles collections, so that they can be filtered, mapped, split and combined.</p>
<p>F# has built in support for this model, as well as for the more traditional approach.</p>
-<h2 id="A-simple-event-stream"><a href="#A-simple-event-stream" class="headerlink" title="A simple event stream"></a>A simple event stream</h2><p>Let’s start with a simple example to compare the two approaches. We’ll implement the classic event handler approach first.</p>
+<h2 id="A-simple-event-stream"><a class="header-anchor" href="#A-simple-event-stream">¶</a>A simple event stream</h2>
+<p>Let’s start with a simple example to compare the two approaches. We’ll implement the classic event handler approach first.</p>
<p>First, we define a utility function that will:</p>
<ul>
<li>create a timer</li>
@@ -218,60 +219,47 @@
</ul>
<p>Here’s the code:</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">open</span> System</span><br><span class="line"><span class="keyword">open</span> System.Threading</span><br><span class="line"></span><br><span class="line"><span class="comment">/// create a timer and register an event handler,</span></span><br><span class="line"><span class="comment">/// then run the timer for five seconds</span></span><br><span class="line"><span class="keyword">let</span> createTimer timerInterval eventHandler <span class="operator">=</span></span><br><span class="line"> <span class="comment">// setup a timer</span></span><br><span class="line"> <span class="keyword">let</span> timer <span class="operator">=</span> <span class="keyword">new</span> System.Timers.Timer(float timerInterval)</span><br><span class="line"> timer.AutoReset <span class="operator">&lt;-</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// add an event handler</span></span><br><span class="line"> timer.Elapsed.Add eventHandler</span><br><span class="line"></span><br><span class="line"> <span class="comment">// return an async task</span></span><br><span class="line"> <span class="keyword">async</span> &#123;</span><br><span class="line"> <span class="comment">// start timer...</span></span><br><span class="line"> timer.Start()</span><br><span class="line"> <span class="comment">// ...run for five seconds...</span></span><br><span class="line"> <span class="keyword">do!</span> Async.Sleep <span class="number">5000</span></span><br><span class="line"> <span class="comment">// ... and stop</span></span><br><span class="line"> timer.Stop()</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>
-
<p>Now test it interactively:</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// create a handler. The event args are ignored</span></span><br><span class="line"><span class="keyword">let</span> basicHandler _ <span class="operator">=</span> <span class="built_in">printfn</span> <span class="string">&quot;tick %A&quot;</span> DateTime.Now</span><br><span class="line"></span><br><span class="line"><span class="comment">// register the handler</span></span><br><span class="line"><span class="keyword">let</span> basicTimer1 <span class="operator">=</span> createTimer <span class="number">1000</span> basicHandler</span><br><span class="line"></span><br><span class="line"><span class="comment">// run the task now</span></span><br><span class="line">Async.RunSynchronously basicTimer1</span><br></pre></td></tr></table></figure>
-
<p>Now let’s create a similar utility method to create a timer, but this time it will return an “observable” as well, which is the stream of events.</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> createTimerAndObservable timerInterval <span class="operator">=</span></span><br><span class="line"> <span class="comment">// setup a timer</span></span><br><span class="line"> <span class="keyword">let</span> timer <span class="operator">=</span> <span class="keyword">new</span> System.Timers.Timer(float timerInterval)</span><br><span class="line"> timer.AutoReset <span class="operator">&lt;-</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// events are automatically IObservable</span></span><br><span class="line"> <span class="keyword">let</span> observable <span class="operator">=</span> timer.Elapsed</span><br><span class="line"></span><br><span class="line"> <span class="comment">// return an async task</span></span><br><span class="line"> <span class="keyword">let</span> task <span class="operator">=</span> <span class="keyword">async</span> &#123;</span><br><span class="line"> timer.Start()</span><br><span class="line"> <span class="keyword">do!</span> Async.Sleep <span class="number">5000</span></span><br><span class="line"> timer.Stop()</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// return a async task and the observable</span></span><br><span class="line"> (task,observable)</span><br></pre></td></tr></table></figure>
-
<p>And again test it interactively:</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// create the timer and the corresponding observable</span></span><br><span class="line"><span class="keyword">let</span> basicTimer2 , timerEventStream <span class="operator">=</span> createTimerAndObservable <span class="number">1000</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// register that every time something happens on the</span></span><br><span class="line"><span class="comment">// event stream, print the time.</span></span><br><span class="line">timerEventStream</span><br><span class="line"><span class="operator">|&gt;</span> Observable.subscribe (<span class="keyword">fun</span> _ <span class="operator">-&gt;</span> <span class="built_in">printfn</span> <span class="string">&quot;tick %A&quot;</span> DateTime.Now)</span><br><span class="line"></span><br><span class="line"><span class="comment">// run the task now</span></span><br><span class="line">Async.RunSynchronously basicTimer2</span><br></pre></td></tr></table></figure>
-
<p>The difference is that instead of registering a handler directly with an event, we are “subscribing” to an event stream. Subtly different, and important.</p>
-<h2 id="Counting-events"><a href="#Counting-events" class="headerlink" title="Counting events"></a>Counting events</h2><p>In this next example, we’ll have a slightly more complex requirement:</p>
+<h2 id="Counting-events"><a class="header-anchor" href="#Counting-events">¶</a>Counting events</h2>
+<p>In this next example, we’ll have a slightly more complex requirement:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Create a timer that ticks every 500ms.</span><br><span class="line">At each tick, print the number of ticks so far and the current time.</span><br></pre></td></tr></table></figure>
-
<p>To do this in a classic imperative way, we would probably create a class with a mutable counter, as below:</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">ImperativeTimerCount</span>() <span class="operator">=</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mutable</span> count <span class="operator">=</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// the event handler. The event args are ignored</span></span><br><span class="line"> <span class="keyword">member</span> this.handleEvent _ <span class="operator">=</span></span><br><span class="line"> count <span class="operator">&lt;-</span> count <span class="operator">+</span> <span class="number">1</span></span><br><span class="line"> <span class="built_in">printfn</span> <span class="string">&quot;timer ticked with count %i&quot;</span> count</span><br></pre></td></tr></table></figure>
-
<p>We can reuse the utility functions we created earlier to test it:</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// create a handler class</span></span><br><span class="line"><span class="keyword">let</span> handler <span class="operator">=</span> <span class="keyword">new</span> ImperativeTimerCount()</span><br><span class="line"></span><br><span class="line"><span class="comment">// register the handler method</span></span><br><span class="line"><span class="keyword">let</span> timerCount1 <span class="operator">=</span> createTimer <span class="number">500</span> handler.handleEvent</span><br><span class="line"></span><br><span class="line"><span class="comment">// run the task now</span></span><br><span class="line">Async.RunSynchronously timerCount1</span><br></pre></td></tr></table></figure>
-
<p>Let’s see how we would do this same thing in a functional way:</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// create the timer and the corresponding observable</span></span><br><span class="line"><span class="keyword">let</span> timerCount2, timerEventStream <span class="operator">=</span> createTimerAndObservable <span class="number">500</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// set up the transformations on the event stream</span></span><br><span class="line">timerEventStream</span><br><span class="line"><span class="operator">|&gt;</span> Observable.scan (<span class="keyword">fun</span> count _ <span class="operator">-&gt;</span> count <span class="operator">+</span> <span class="number">1</span>) <span class="number">0</span></span><br><span class="line"><span class="operator">|&gt;</span> Observable.subscribe (<span class="keyword">fun</span> count <span class="operator">-&gt;</span> <span class="built_in">printfn</span> <span class="string">&quot;timer ticked with count %i&quot;</span> count)</span><br><span class="line"></span><br><span class="line"><span class="comment">// run the task now</span></span><br><span class="line">Async.RunSynchronously timerCount2</span><br></pre></td></tr></table></figure>
-
<p>Here we see how you can build up layers of event transformations, just as you do with list transformations in LINQ.</p>
<p>The first transformation is <code>scan</code>, which accumulates state for each event. It is roughly equivalent to the <code>List.fold</code> function that we have seen used with lists. In this case, the accumulated state is just a counter.</p>
<p>And then, for each event, the count is printed out.</p>
<p>Note that in this functional approach, we didn’t have any mutable state, and we didn’t need to create any special classes.</p>
-<h2 id="Merging-multiple-event-streams"><a href="#Merging-multiple-event-streams" class="headerlink" title="Merging multiple event streams"></a>Merging multiple event streams</h2><p>For a final example, we’ll look at merging multiple event streams.</p>
+<h2 id="Merging-multiple-event-streams"><a class="header-anchor" href="#Merging-multiple-event-streams">¶</a>Merging multiple event streams</h2>
+<p>For a final example, we’ll look at merging multiple event streams.</p>
<p>Let’s make a requirement based on the well-known “FizzBuzz” problem:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Create two timers, called &#x27;3&#x27; and &#x27;5&#x27;. The &#x27;3&#x27; timer ticks every 300ms and the &#x27;5&#x27; timer ticks</span><br><span class="line">every 500ms.</span><br><span class="line"></span><br><span class="line">Handle the events as follows:</span><br><span class="line">a) for all events, print the id of the time and the time</span><br><span class="line">b) when a tick is simultaneous with a previous tick, print &#x27;FizzBuzz&#x27;</span><br><span class="line">otherwise:</span><br><span class="line">c) when the &#x27;3&#x27; timer ticks on its own, print &#x27;Fizz&#x27;</span><br><span class="line">d) when the &#x27;5&#x27; timer ticks on its own, print &#x27;Buzz&#x27;</span><br></pre></td></tr></table></figure>
-
<p>First let’s create some code that both implementations can use.</p>
<p>We’ll want a generic event type that captures the timer id and the time of the tick.</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">FizzBuzzEvent</span> <span class="operator">=</span> &#123;label<span class="operator">:</span><span class="type">int</span>; time<span class="operator">:</span> DateTime&#125;</span><br></pre></td></tr></table></figure>
-
<p>And then we need a utility function to see if two events are simultaneous. We’ll be generous and allow a time difference of up to 50ms.</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> areSimultaneous (earlierEvent,laterEvent) <span class="operator">=</span></span><br><span class="line"> <span class="keyword">let</span> &#123;label<span class="operator">=</span>_;time<span class="operator">=</span>t1&#125; <span class="operator">=</span> earlierEvent</span><br><span class="line"> <span class="keyword">let</span> &#123;label<span class="operator">=</span>_;time<span class="operator">=</span>t2&#125; <span class="operator">=</span> laterEvent</span><br><span class="line"> t2.Subtract(t1).Milliseconds <span class="operator">&lt;</span> <span class="number">50</span></span><br></pre></td></tr></table></figure>
-
<p>In the imperative design, we’ll need to keep track of the previous event, so we can compare them. And we’ll need special case code for the first time, when the previous event doesn’t exist</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">ImperativeFizzBuzzHandler</span>() <span class="operator">=</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mutable</span> previousEvent<span class="operator">:</span> FizzBuzzEvent <span class="type">option</span> <span class="operator">=</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> printEvent thisEvent <span class="operator">=</span></span><br><span class="line"> <span class="keyword">let</span> &#123;label<span class="operator">=</span><span class="built_in">id</span>; time<span class="operator">=</span>t&#125; <span class="operator">=</span> thisEvent</span><br><span class="line"> <span class="built_in">printf</span> <span class="string">&quot;[%i] %i.%03i &quot;</span> <span class="built_in">id</span> t.Second t.Millisecond</span><br><span class="line"> <span class="keyword">let</span> simultaneous <span class="operator">=</span> previousEvent.IsSome <span class="operator">&amp;&amp;</span> areSimultaneous (previousEvent.Value,thisEvent)</span><br><span class="line"> <span class="keyword">if</span> simultaneous <span class="keyword">then</span> <span class="built_in">printfn</span> <span class="string">&quot;FizzBuzz&quot;</span></span><br><span class="line"> <span class="keyword">elif</span> <span class="built_in">id</span> <span class="operator">=</span> <span class="number">3</span> <span class="keyword">then</span> <span class="built_in">printfn</span> <span class="string">&quot;Fizz&quot;</span></span><br><span class="line"> <span class="keyword">elif</span> <span class="built_in">id</span> <span class="operator">=</span> <span class="number">5</span> <span class="keyword">then</span> <span class="built_in">printfn</span> <span class="string">&quot;Buzz&quot;</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">member</span> this.handleEvent3 eventArgs <span class="operator">=</span></span><br><span class="line"> <span class="keyword">let</span> event <span class="operator">=</span> &#123;label<span class="operator">=</span><span class="number">3</span>; time<span class="operator">=</span>DateTime.Now&#125;</span><br><span class="line"> printEvent event</span><br><span class="line"> previousEvent <span class="operator">&lt;-</span> <span class="literal">Some</span> event</span><br><span class="line"></span><br><span class="line"> <span class="keyword">member</span> this.handleEvent5 eventArgs <span class="operator">=</span></span><br><span class="line"> <span class="keyword">let</span> event <span class="operator">=</span> &#123;label<span class="operator">=</span><span class="number">5</span>; time<span class="operator">=</span>DateTime.Now&#125;</span><br><span class="line"> printEvent event</span><br><span class="line"> previousEvent <span class="operator">&lt;-</span> <span class="literal">Some</span> event</span><br></pre></td></tr></table></figure>
-
<p>Now the code is beginning to get ugly fast! Already we have mutable state, complex conditional logic, and special cases, just for such a simple requirement.</p>
<p>Let’s test it:</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// create the class</span></span><br><span class="line"><span class="keyword">let</span> handler <span class="operator">=</span> <span class="keyword">new</span> ImperativeFizzBuzzHandler()</span><br><span class="line"></span><br><span class="line"><span class="comment">// create the two timers and register the two handlers</span></span><br><span class="line"><span class="keyword">let</span> timer3 <span class="operator">=</span> createTimer <span class="number">300</span> handler.handleEvent3</span><br><span class="line"><span class="keyword">let</span> timer5 <span class="operator">=</span> createTimer <span class="number">500</span> handler.handleEvent5</span><br><span class="line"></span><br><span class="line"><span class="comment">// run the two timers at the same time</span></span><br><span class="line">[timer3;timer5]</span><br><span class="line"><span class="operator">|&gt;</span> Async.Parallel</span><br><span class="line"><span class="operator">|&gt;</span> Async.RunSynchronously</span><br></pre></td></tr></table></figure>
-
<p>It does work, but are you sure the code is not buggy? Are you likely to accidentally break something if you change it?</p>
<p>The problem with this imperative code is that it has a lot of noise that obscures the the requirements.</p>
<p>Can the functional version do better? Let’s see!</p>
<p>First, we create <em>two</em> event streams, one for each timer:</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> timer3, timerEventStream3 <span class="operator">=</span> createTimerAndObservable <span class="number">300</span></span><br><span class="line"><span class="keyword">let</span> timer5, timerEventStream5 <span class="operator">=</span> createTimerAndObservable <span class="number">500</span></span><br></pre></td></tr></table></figure>
-
<p>Next, we convert each event on the “raw” event streams into our FizzBuzz event type:</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// convert the time events into FizzBuzz events with the appropriate id</span></span><br><span class="line"><span class="keyword">let</span> eventStream3 <span class="operator">=</span></span><br><span class="line"> timerEventStream3</span><br><span class="line"> <span class="operator">|&gt;</span> Observable.map (<span class="keyword">fun</span> _ <span class="operator">-&gt;</span> &#123;label<span class="operator">=</span><span class="number">3</span>; time<span class="operator">=</span>DateTime.Now&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> eventStream5 <span class="operator">=</span></span><br><span class="line"> timerEventStream5</span><br><span class="line"> <span class="operator">|&gt;</span> Observable.map (<span class="keyword">fun</span> _ <span class="operator">-&gt;</span> &#123;label<span class="operator">=</span><span class="number">5</span>; time<span class="operator">=</span>DateTime.Now&#125;)</span><br></pre></td></tr></table></figure>
-
<p>Now, to see if two events are simultaneous, we need to compare them from the two different streams somehow.</p>
<p>It’s actually easier than it sounds, because we can:</p>
<ul>
@@ -282,26 +270,21 @@
</ul>
<p>Here’s the actual code to do this:</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// combine the two streams</span></span><br><span class="line"><span class="keyword">let</span> combinedStream <span class="operator">=</span></span><br><span class="line"> Observable.merge eventStream3 eventStream5</span><br><span class="line"></span><br><span class="line"><span class="comment">// make pairs of events</span></span><br><span class="line"><span class="keyword">let</span> pairwiseStream <span class="operator">=</span></span><br><span class="line"> combinedStream <span class="operator">|&gt;</span> Observable.pairwise</span><br><span class="line"></span><br><span class="line"><span class="comment">// split the stream based on whether the pairs are simultaneous</span></span><br><span class="line"><span class="keyword">let</span> simultaneousStream, nonSimultaneousStream <span class="operator">=</span></span><br><span class="line"> pairwiseStream <span class="operator">|&gt;</span> Observable.partition areSimultaneous</span><br></pre></td></tr></table></figure>
-
<p>Finally, we can split the <code>nonSimultaneousStream</code> again, based on the event id:</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// split the non-simultaneous stream based on the id</span></span><br><span class="line"><span class="keyword">let</span> fizzStream, buzzStream <span class="operator">=</span></span><br><span class="line"> nonSimultaneousStream</span><br><span class="line"> <span class="comment">// convert pair of events to the first event</span></span><br><span class="line"> <span class="operator">|&gt;</span> Observable.map (<span class="keyword">fun</span> (ev1,_) <span class="operator">-&gt;</span> ev1)</span><br><span class="line"> <span class="comment">// split on whether the event id is three</span></span><br><span class="line"> <span class="operator">|&gt;</span> Observable.partition (<span class="keyword">fun</span> &#123;label<span class="operator">=</span><span class="built_in">id</span>&#125; <span class="operator">-&gt;</span> <span class="built_in">id</span><span class="operator">=</span><span class="number">3</span>)</span><br></pre></td></tr></table></figure>
-
<p>Let’s review so far. We have started with the two original event streams and from them created four new ones:</p>
<ul>
<li><code>combinedStream</code> contains all the events</li>
<li><code>simultaneousStream</code> contains only the simultaneous events</li>
-<li><code>fizzStream</code> contains only the non-simultaneous events with id&#x3D;3</li>
-<li><code>buzzStream</code> contains only the non-simultaneous events with id&#x3D;5</li>
+<li><code>fizzStream</code> contains only the non-simultaneous events with id=3</li>
+<li><code>buzzStream</code> contains only the non-simultaneous events with id=5</li>
</ul>
<p>Now all we need to do is attach behavior to each stream:</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//print events from the combinedStream</span></span><br><span class="line">combinedStream</span><br><span class="line"><span class="operator">|&gt;</span> Observable.subscribe (<span class="keyword">fun</span> &#123;label<span class="operator">=</span><span class="built_in">id</span>;time<span class="operator">=</span>t&#125; <span class="operator">-&gt;</span></span><br><span class="line"> <span class="built_in">printf</span> <span class="string">&quot;[%i] %i.%03i &quot;</span> <span class="built_in">id</span> t.Second t.Millisecond)</span><br><span class="line"></span><br><span class="line"><span class="comment">//print events from the simultaneous stream</span></span><br><span class="line">simultaneousStream</span><br><span class="line"><span class="operator">|&gt;</span> Observable.subscribe (<span class="keyword">fun</span> _ <span class="operator">-&gt;</span> <span class="built_in">printfn</span> <span class="string">&quot;FizzBuzz&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">//print events from the nonSimultaneous streams</span></span><br><span class="line">fizzStream</span><br><span class="line"><span class="operator">|&gt;</span> Observable.subscribe (<span class="keyword">fun</span> _ <span class="operator">-&gt;</span> <span class="built_in">printfn</span> <span class="string">&quot;Fizz&quot;</span>)</span><br><span class="line"></span><br><span class="line">buzzStream</span><br><span class="line"><span class="operator">|&gt;</span> Observable.subscribe (<span class="keyword">fun</span> _ <span class="operator">-&gt;</span> <span class="built_in">printfn</span> <span class="string">&quot;Buzz&quot;</span>)</span><br></pre></td></tr></table></figure>
-
<p>Let’s test it:</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// run the two timers at the same time</span></span><br><span class="line">[timer3;timer5]</span><br><span class="line"><span class="operator">|&gt;</span> Async.Parallel</span><br><span class="line"><span class="operator">|&gt;</span> Async.RunSynchronously</span><br></pre></td></tr></table></figure>
-
<p>Here’s all the code in one complete set:</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// create the event streams and raw observables</span></span><br><span class="line"><span class="keyword">let</span> timer3, timerEventStream3 <span class="operator">=</span> createTimerAndObservable <span class="number">300</span></span><br><span class="line"><span class="keyword">let</span> timer5, timerEventStream5 <span class="operator">=</span> createTimerAndObservable <span class="number">500</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// convert the time events into FizzBuzz events with the appropriate id</span></span><br><span class="line"><span class="keyword">let</span> eventStream3 <span class="operator">=</span> timerEventStream3</span><br><span class="line"> <span class="operator">|&gt;</span> Observable.map (<span class="keyword">fun</span> _ <span class="operator">-&gt;</span> &#123;label<span class="operator">=</span><span class="number">3</span>; time<span class="operator">=</span>DateTime.Now&#125;)</span><br><span class="line"><span class="keyword">let</span> eventStream5 <span class="operator">=</span> timerEventStream5</span><br><span class="line"> <span class="operator">|&gt;</span> Observable.map (<span class="keyword">fun</span> _ <span class="operator">-&gt;</span> &#123;label<span class="operator">=</span><span class="number">5</span>; time<span class="operator">=</span>DateTime.Now&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// combine the two streams</span></span><br><span class="line"><span class="keyword">let</span> combinedStream <span class="operator">=</span></span><br><span class="line"> Observable.merge eventStream3 eventStream5</span><br><span class="line"></span><br><span class="line"><span class="comment">// make pairs of events</span></span><br><span class="line"><span class="keyword">let</span> pairwiseStream <span class="operator">=</span></span><br><span class="line"> combinedStream <span class="operator">|&gt;</span> Observable.pairwise</span><br><span class="line"></span><br><span class="line"><span class="comment">// split the stream based on whether the pairs are simultaneous</span></span><br><span class="line"><span class="keyword">let</span> simultaneousStream, nonSimultaneousStream <span class="operator">=</span></span><br><span class="line"> pairwiseStream <span class="operator">|&gt;</span> Observable.partition areSimultaneous</span><br><span class="line"></span><br><span class="line"><span class="comment">// split the non-simultaneous stream based on the id</span></span><br><span class="line"><span class="keyword">let</span> fizzStream, buzzStream <span class="operator">=</span></span><br><span class="line"> nonSimultaneousStream</span><br><span class="line"> <span class="comment">// convert pair of events to the first event</span></span><br><span class="line"> <span class="operator">|&gt;</span> Observable.map (<span class="keyword">fun</span> (ev1,_) <span class="operator">-&gt;</span> ev1)</span><br><span class="line"> <span class="comment">// split on whether the event id is three</span></span><br><span class="line"> <span class="operator">|&gt;</span> Observable.partition (<span class="keyword">fun</span> &#123;label<span class="operator">=</span><span class="built_in">id</span>&#125; <span class="operator">-&gt;</span> <span class="built_in">id</span><span class="operator">=</span><span class="number">3</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">//print events from the combinedStream</span></span><br><span class="line">combinedStream</span><br><span class="line"><span class="operator">|&gt;</span> Observable.subscribe (<span class="keyword">fun</span> &#123;label<span class="operator">=</span><span class="built_in">id</span>;time<span class="operator">=</span>t&#125; <span class="operator">-&gt;</span></span><br><span class="line"> <span class="built_in">printf</span> <span class="string">&quot;[%i] %i.%03i &quot;</span> <span class="built_in">id</span> t.Second t.Millisecond)</span><br><span class="line"></span><br><span class="line"><span class="comment">//print events from the simultaneous stream</span></span><br><span class="line">simultaneousStream</span><br><span class="line"><span class="operator">|&gt;</span> Observable.subscribe (<span class="keyword">fun</span> _ <span class="operator">-&gt;</span> <span class="built_in">printfn</span> <span class="string">&quot;FizzBuzz&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">//print events from the nonSimultaneous streams</span></span><br><span class="line">fizzStream</span><br><span class="line"><span class="operator">|&gt;</span> Observable.subscribe (<span class="keyword">fun</span> _ <span class="operator">-&gt;</span> <span class="built_in">printfn</span> <span class="string">&quot;Fizz&quot;</span>)</span><br><span class="line"></span><br><span class="line">buzzStream</span><br><span class="line"><span class="operator">|&gt;</span> Observable.subscribe (<span class="keyword">fun</span> _ <span class="operator">-&gt;</span> <span class="built_in">printfn</span> <span class="string">&quot;Buzz&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// run the two timers at the same time</span></span><br><span class="line">[timer3;timer5]</span><br><span class="line"><span class="operator">|&gt;</span> Async.Parallel</span><br><span class="line"><span class="operator">|&gt;</span> Async.RunSynchronously</span><br></pre></td></tr></table></figure>
-
<p>The code might seem a bit long winded, but this kind of incremental, step-wise approach is very clear and self-documenting.</p>
<p>Some of the benefits of this style are:</p>
<ul>
@@ -311,9 +294,9 @@
<li>It is easy to debug. For example, I could easily “tap” the output of the <code>simultaneousStream</code> to see if it contains what I think it contains:</li>
</ul>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// debugging code</span></span><br><span class="line"><span class="comment">//simultaneousStream |&gt; Observable.subscribe (fun e -&gt; printfn &quot;sim %A&quot; e)</span></span><br><span class="line"><span class="comment">//nonSimultaneousStream |&gt; Observable.subscribe (fun e -&gt; printfn &quot;non-sim %A&quot; e)</span></span><br></pre></td></tr></table></figure>
-
<p>This would be much harder in the imperative version.</p>
-<h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Functional Reactive Programming (known as FRP) is a big topic, and we’ve only just touched on it here. I hope this introduction has given you a glimpse of the usefulness of this way of doing things.</p>
+<h2 id="Summary"><a class="header-anchor" href="#Summary">¶</a>Summary</h2>
+<p>Functional Reactive Programming (known as FRP) is a big topic, and we’ve only just touched on it here. I hope this introduction has given you a glimpse of the usefulness of this way of doing things.</p>
<p>If you want to learn more, see the documentation for the F# <a target="_blank" rel="noopener" href="https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/ee370313%28v=vs.100%29?redirectedfrom=MSDN">Observable module</a>, which has the basic transformations used above. And there is also the <a target="_blank" rel="noopener" href="https://docs.microsoft.com/en-us/previous-versions/dotnet/reactive-extensions/hh242985%28v=vs.103%29">Reactive Extensions (Rx)</a> library which shipped as part of .NET 4. That contains many other additional transformations.</p>
</div>
diff --git a/2024/09/15/Advanced-C-binding-using-ocaml-ctypes-and-dune/index.html b/2024/09/15/Advanced-C-binding-using-ocaml-ctypes-and-dune/index.html
index 9f8963b8..a825fda7 100644
--- a/2024/09/15/Advanced-C-binding-using-ocaml-ctypes-and-dune/index.html
+++ b/2024/09/15/Advanced-C-binding-using-ocaml-ctypes-and-dune/index.html
@@ -200,21 +200,26 @@
<div class="post-content">
<p>I was working on a OCaml binding for <a target="_blank" rel="noopener" href="https://github.com/Haivision/srt">libsrt</a> last summer, to add support for SRT real-time input and output to <a target="_blank" rel="noopener" href="https://github.com/savonet/liquidsoap">liquidsoap</a>, and came across the need to access the <code>[sys/socket.h](https://pubs.opengroup.org/onlinepubs/7908799/xns/syssocket.h.html)</code> C API.</p>
<p>I had already decided to use the very elegant <code>[ocaml-ctypes](https://github.com/ocamllabs/ocaml-ctypes)</code> module for the SRT binding so I went with it and created a <code>[ocaml-sys-socket](https://github.com/toots/ocaml-sys-socket)</code> module using it as well. It was a very interesting experience that I would like to describe here!</p>
-<h1 id="ocaml-ctypes"><a href="#ocaml-ctypes" class="headerlink" title="ocaml-ctypes"></a>ocaml-ctypes</h1><p>The idea behind OCaml ctypes is to create a binding against a C library without having to write C code, or as least as possible. The most straight-forward way of using it is via <code>[libffi](https://github.com/libffi/libffi)</code> , providing access to dynamically-loaded libraries.</p>
+<h1>ocaml-ctypes</h1>
+<p>The idea behind OCaml ctypes is to create a binding against a C library without having to write C code, or as least as possible. The most straight-forward way of using it is via <code>[libffi](https://github.com/libffi/libffi)</code> , providing access to dynamically-loaded libraries.</p>
<p>The second way of using it is by letting the module generate the basic C stubs required to build and link against a shared library. This is the mode that we’re going to use here. In this mode, the programmer has to describe the C headers of the library they intent to bind to using dedicated OCaml modules, operators and types. From that description, ocaml-ctypes is able to generate the required glue for the binding.</p>
<p>One advantage of using ocaml-ctypes is that the created bindings make as few assumptions as possible about the <a target="_blank" rel="noopener" href="https://caml.inria.fr/pub/docs/manual-ocaml/intfc.html">OCaml C interfacing API</a>. This is pretty nice, in particular since the <a target="_blank" rel="noopener" href="https://github.com/ocaml/ocaml">OCaml compiler</a> is moving pretty quickly these days (which is awesome!) and also if, perhaps one day, <a target="_blank" rel="noopener" href="https://github.com/ocaml-multicore/ocaml-multicore">support for multi-core</a> is added to the compiler, which will undoubtedly change the C interface API quite a bit.</p>
-<h1 id="dune"><a href="#dune" class="headerlink" title="dune"></a>dune</h1><p><code>[dune](https://github.com/ocaml/dune)</code> (formally <code>jbuilder</code> ) is a build system for OCaml projects that has recently raised to much popularity, particularly due to its tight integration with the rest of the OCaml ecosystem, such as <code>[ocamlfind](http://projects.camlcity.org/projects/findlib.html)</code> and <code>[opam](https://opam.ocaml.org/)</code> .</p>
+<h1>dune</h1>
+<p><code>[dune](https://github.com/ocaml/dune)</code> (formally <code>jbuilder</code> ) is a build system for OCaml projects that has recently raised to much popularity, particularly due to its tight integration with the rest of the OCaml ecosystem, such as <code>[ocamlfind](http://projects.camlcity.org/projects/findlib.html)</code> and <code>[opam](https://opam.ocaml.org/)</code> .</p>
<p>My personal motto in programming in general is that <em>“Simple things should be simple, but complex things should be possible”</em>. <code>dune</code> certainly does not fit into that category but, rather, makes some complex things extremely easy to setup. It’s the kind of tool that will make your life incredibly easier when what you intent to do fits well within their workflow but might not be easy to bend to some very specific niche use. We will see one such case below.</p>
<p>At any rate, it’s been an amazing experience getting to learn how to use <code>dune</code> and the resulting code and build system is remarkably short and elegant, yet very powerful.</p>
-<h1 id="socket-h"><a href="#socket-h" class="headerlink" title="socket.h"></a>socket.h</h1><p><code>socket.h</code> is the Unix header that describes the C API to various socket operations, IP version 4 and 6 as well as unix file sockets. There is also a windows API mimicking it, which makes most code using it easily portable to windows.</p>
+<h1>socket.h</h1>
+<p><code>socket.h</code> is the Unix header that describes the C API to various socket operations, IP version 4 and 6 as well as unix file sockets. There is also a windows API mimicking it, which makes most code using it easily portable to windows.</p>
<p>Most network-based C libraries refer to <code>socket.h</code> to describe the type of socket that can be used with their API so it’s an important entry point for a lot of network operations and one that would be nice to support as generically as possible in OCaml.</p>
<p>The catch, though, is that, most likely for historical reasons¹, the <a target="_blank" rel="noopener" href="https://pubs.opengroup.org/onlinepubs/7908799/xns/syssocket.h.html">POSIX specifications</a> only <em>partially</em> defines some of the required data structures and types, which makes it possible to write C code using them but does not give enough information to write C bindings without having to use the compiler to parse the actual system-specific headers of the running host.</p>
<p>For instance, here’s how the <code>sockaddr</code> structure is specified:</p>
-<p>The <em>&lt;sys&#x2F;socket.h&gt;</em> header defines the sockaddr structure that includes at least the following members:sa_family_t sa_family address family<br>char sa_data[] socket address (variable-length data)</p>
+<p>The <em>&lt;sys/socket.h&gt;</em> header defines the sockaddr structure that includes at least the following members:sa_family_t sa_family address family<br>
+char sa_data[] socket address (variable-length data)</p>
<p>Likewise, here’s what is specified about the size of the <code>socklen_t</code> data type:</p>
-<p><em>&lt;sys&#x2F;socket.h&gt;</em> makes available a type, socklen_t, which is an unsigned opaque integral type of length of at least 32 bits.</p>
+<p><em>&lt;sys/socket.h&gt;</em> makes available a type, socklen_t, which is an unsigned opaque integral type of length of at least 32 bits.</p>
<p>Thus, in order to know the exact offset of <code>sa_family</code> inside the <code>sockaddr</code> structure or the actual size of a <code>socklen_t</code> integer, one has to include the OS-specific header, parse its definitions for that specific OS and, only then, is it possible to compute that offset or data size. Let’s see how it’s done in our binding now!</p>
-<h1 id="Putting-it-together"><a href="#Putting-it-together" class="headerlink" title="Putting it together"></a>Putting it together</h1><p>The C binding requires 4 separate passes:</p>
+<h1>Putting it together</h1>
+<p>The C binding requires 4 separate passes:</p>
<ul>
<li>The <code>[constants](https://github.com/toots/ocaml-sys-socket/tree/master/src/sys-socket/constants)</code> pass, which computes and exports some specific constant and data sizes, computed from the C headers</li>
<li>The <code>[types](https://github.com/toots/ocaml-sys-socket/tree/master/src/sys-socket/types)</code> pass, which, given the system-specific constants and sizes exported in the previous phase, defines the actual C data structure bindings.</li>
@@ -222,73 +227,62 @@
<li>Finally, the <a target="_blank" rel="noopener" href="https://github.com/toots/ocaml-sys-socket/blob/master/src/sys-socket/sys_socket.mli">last pass</a> does a cleanup of the <code>stubs</code> pass to export a relevant and OCaml- (and <code>ocaml-ctypes</code>) specific public API that is to be used by users of the module.</li>
</ul>
<p><code>dune</code> makes each of these steps fairly easy to integrate into the next one, defining compilation elements and binaries to build before moving to the next pass.</p>
-<h2 id="Constants-pass"><a href="#Constants-pass" class="headerlink" title="Constants pass"></a>Constants pass</h2><p>During that pass, we compute and export all required C values defined in the headers. We also add our own constants, which give us the sizes that the POSIX specifications leave up to the OS. Here’s the OCaml code for it:</p>
+<h2 id="Constants-pass"><a class="header-anchor" href="#Constants-pass">¶</a>Constants pass</h2>
+<p>During that pass, we compute and export all required C values defined in the headers. We also add our own constants, which give us the sizes that the POSIX specifications leave up to the OS. Here’s the OCaml code for it:</p>
<figure class="highlight ocaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">module</span> <span class="type">Def</span> (<span class="type">S</span> : <span class="type">Cstubs</span>.<span class="type">Types</span>.<span class="type">TYPE</span>) = <span class="keyword">struct</span></span><br><span class="line"> <span class="keyword">let</span> af_inet = <span class="type">S</span>.constant <span class="string">&quot;AF_INET&quot;</span> <span class="type">S</span>.<span class="built_in">int</span></span><br><span class="line"> <span class="keyword">let</span> af_inet6 = <span class="type">S</span>.constant <span class="string">&quot;AF_INET6&quot;</span> <span class="type">S</span>.<span class="built_in">int</span></span><br><span class="line"> <span class="keyword">let</span> af_unix = <span class="type">S</span>.constant <span class="string">&quot;AF_UNIX&quot;</span> <span class="type">S</span>.<span class="built_in">int</span></span><br><span class="line"> <span class="keyword">let</span> af_unspec = <span class="type">S</span>.constant <span class="string">&quot;AF_UNSPEC&quot;</span> <span class="type">S</span>.<span class="built_in">int</span></span><br><span class="line"> <span class="keyword">let</span> sa_data_len = <span class="type">S</span>.constant <span class="string">&quot;SA_DATA_LEN&quot;</span> <span class="type">S</span>.<span class="built_in">int</span></span><br><span class="line"> <span class="keyword">let</span> sa_family_len = <span class="type">S</span>.constant <span class="string">&quot;SA_FAMILY_LEN&quot;</span> <span class="type">S</span>.<span class="built_in">int</span></span><br><span class="line"> <span class="keyword">let</span> sock_dgram = <span class="type">S</span>.constant <span class="string">&quot;SOCK_DGRAM&quot;</span> <span class="type">S</span>.<span class="built_in">int</span></span><br><span class="line"> <span class="keyword">let</span> sock_stream = <span class="type">S</span>.constant <span class="string">&quot;SOCK_STREAM&quot;</span> <span class="type">S</span>.<span class="built_in">int</span></span><br><span class="line"> <span class="keyword">let</span> sock_seqpacket = <span class="type">S</span>.constant <span class="string">&quot;SOCK_STREAM&quot;</span> <span class="type">S</span>.<span class="built_in">int</span></span><br><span class="line"> <span class="keyword">let</span> socklen_t_len = <span class="type">S</span>.constant <span class="string">&quot;SOCKLEN_T_LEN&quot;</span> <span class="type">S</span>.<span class="built_in">int</span></span><br><span class="line"> <span class="keyword">let</span> ni_maxserv = <span class="type">S</span>.constant <span class="string">&quot;NI_MAXSERV&quot;</span> <span class="type">S</span>.<span class="built_in">int</span></span><br><span class="line"> <span class="keyword">let</span> ni_maxhost = <span class="type">S</span>.constant <span class="string">&quot;NI_MAXHOST&quot;</span> <span class="type">S</span>.<span class="built_in">int</span></span><br><span class="line"> <span class="keyword">let</span> ni_numerichost = <span class="type">S</span>.constant <span class="string">&quot;NI_NUMERICHOST&quot;</span> <span class="type">S</span>.<span class="built_in">int</span></span><br><span class="line"> <span class="keyword">let</span> ni_numericserv = <span class="type">S</span>.constant <span class="string">&quot;NI_NUMERICSERV&quot;</span> <span class="type">S</span>.<span class="built_in">int</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
-
<p>Pretty straightforward! Some of these constants are defined by the POSIX headers and some are custom defined for our needs, for instance <code>SOCKLEN_T_LEN</code> . Here’s how they are extracted, using the <code>dune</code> build configuration for <code>[gen_constants_c](https://github.com/toots/ocaml-sys-socket/blob/master/src/sys-socket/generator/gen_constants_c.ml)</code>:</p>
<figure class="highlight ocaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> c_headers = <span class="string">&quot;</span></span><br><span class="line"><span class="string">#ifdef _WIN32</span></span><br><span class="line"><span class="string"> #include &lt;winsock2.h&gt;</span></span><br><span class="line"><span class="string"> #include &lt;ws2tcpip.h&gt;</span></span><br><span class="line"><span class="string">#else</span></span><br><span class="line"><span class="string"> #include &lt;sys/socket.h&gt;</span></span><br><span class="line"><span class="string"> #include &lt;sys/un.h&gt;</span></span><br><span class="line"><span class="string"> #include &lt;netdb.h&gt;</span></span><br><span class="line"><span class="string">#endif</span></span><br><span class="line"><span class="string">#define SA_DATA_LEN (sizeof(((struct sockaddr*)0)-&gt;sa_data))</span></span><br><span class="line"><span class="string">#define SA_FAMILY_LEN (sizeof(((struct sockaddr*)0)-&gt;sa_family))</span></span><br><span class="line"><span class="string">#define SOCKLEN_T_LEN (sizeof(socklen_t))</span></span><br><span class="line"><span class="string">#ifndef NI_MAXHOST</span></span><br><span class="line"><span class="string"> #define NI_MAXHOST 1025</span></span><br><span class="line"><span class="string">#endif</span></span><br><span class="line"><span class="string">#ifndef NI_MAXSERV</span></span><br><span class="line"><span class="string"> #define NI_MAXSERV 32</span></span><br><span class="line"><span class="string">#endif</span></span><br><span class="line"><span class="string">&quot;</span></span><br><span class="line"><span class="keyword">let</span> <span class="literal">()</span> =</span><br><span class="line"> <span class="keyword">let</span> fname = <span class="type">Sys</span>.argv.(<span class="number">1</span>) <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">let</span> oc = open_out_bin fname <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">let</span> format =</span><br><span class="line"> <span class="type">Format</span>.formatter_of_out_channel oc</span><br><span class="line"> <span class="keyword">in</span></span><br><span class="line"> <span class="type">Format</span>.fprintf format <span class="string">&quot;%s@\n&quot;</span> c_headers;</span><br><span class="line"> <span class="type">Cstubs</span>.<span class="type">Types</span>.write_c format (<span class="keyword">module</span> <span class="type">Sys_socket_constants</span>.<span class="type">Def</span>);</span><br><span class="line"> <span class="type">Format</span>.pp_print_flush format <span class="literal">()</span>;</span><br><span class="line"> close_out oc</span><br></pre></td></tr></table></figure>
-
<p>This OCaml code makes use of <code>ocaml-ctypes</code> to build a binary that exports the OCaml interface defined by <code>Sys_socket_constants.Def</code> . Once compiled, its output looks like this:</p>
<figure class="highlight ocaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">include</span> <span class="type">Ctypes</span></span><br><span class="line"><span class="keyword">let</span> lift x = x</span><br><span class="line"><span class="keyword">open</span> <span class="type">Ctypes_static</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="keyword">rec</span> field : <span class="keyword">type</span> t a. t typ -&gt; <span class="built_in">string</span> -&gt; a typ -&gt; (a, t) field =</span><br><span class="line"> <span class="keyword">fun</span> s fname ftype -&gt; <span class="keyword">match</span> s, fname <span class="keyword">with</span></span><br><span class="line"> | <span class="type">View</span> &#123; ty &#125;, _ -&gt;</span><br><span class="line"> <span class="keyword">let</span> &#123; ftype; foffset; fname &#125; = field ty fname ftype <span class="keyword">in</span></span><br><span class="line"> &#123; ftype; foffset; fname &#125;</span><br><span class="line"> | _ -&gt; failwith (<span class="string">&quot;Unexpected field &quot;</span>^ fname)</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="keyword">rec</span> seal : <span class="keyword">type</span> a. a typ -&gt; <span class="built_in">unit</span> = <span class="keyword">function</span></span><br><span class="line"> | <span class="type">Struct</span> &#123; tag; spec = <span class="type">Complete</span> _ &#125; -&gt;</span><br><span class="line"> raise (<span class="type">ModifyingSealedType</span> tag)</span><br><span class="line"> | <span class="type">Union</span> &#123; utag; uspec = <span class="type">Some</span> _ &#125; -&gt;</span><br><span class="line"> raise (<span class="type">ModifyingSealedType</span> utag)</span><br><span class="line"> | <span class="type">View</span> &#123; ty &#125; -&gt; seal ty</span><br><span class="line"> | _ -&gt;</span><br><span class="line"> raise (<span class="type">Unsupported</span> <span class="string">&quot;Sealing a non-structured type&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="symbol">&#x27;a</span> const = <span class="symbol">&#x27;a</span></span><br><span class="line"><span class="keyword">let</span> constant (<span class="keyword">type</span> t) name (t : t typ) : t = <span class="keyword">match</span> t, name <span class="keyword">with</span></span><br><span class="line"> | <span class="type">Ctypes_static</span>.<span class="type">Primitive</span> <span class="type">Cstubs_internals</span>.<span class="type">Int</span>, <span class="string">&quot;NI_NUMERICSERV&quot;</span> -&gt;</span><br><span class="line"> <span class="number">8</span></span><br><span class="line"> | <span class="type">Ctypes_static</span>.<span class="type">Primitive</span> <span class="type">Cstubs_internals</span>.<span class="type">Int</span>, <span class="string">&quot;NI_NUMERICHOST&quot;</span> -&gt;</span><br><span class="line"> <span class="number">2</span></span><br><span class="line"> | <span class="type">Ctypes_static</span>.<span class="type">Primitive</span> <span class="type">Cstubs_internals</span>.<span class="type">Int</span>, <span class="string">&quot;NI_MAXHOST&quot;</span> -&gt;</span><br><span class="line"> <span class="number">1025</span></span><br><span class="line"> | <span class="type">Ctypes_static</span>.<span class="type">Primitive</span> <span class="type">Cstubs_internals</span>.<span class="type">Int</span>, <span class="string">&quot;NI_MAXSERV&quot;</span> -&gt;</span><br><span class="line"> <span class="number">32</span></span><br><span class="line"> | <span class="type">Ctypes_static</span>.<span class="type">Primitive</span> <span class="type">Cstubs_internals</span>.<span class="type">Int</span>, <span class="string">&quot;SOCKLEN_T_LEN&quot;</span> -&gt;</span><br><span class="line"> <span class="number">4</span></span><br><span class="line"> | <span class="type">Ctypes_static</span>.<span class="type">Primitive</span> <span class="type">Cstubs_internals</span>.<span class="type">Int</span>, <span class="string">&quot;SOCK_STREAM&quot;</span> -&gt;</span><br><span class="line"> <span class="number">1</span></span><br><span class="line"> | <span class="type">Ctypes_static</span>.<span class="type">Primitive</span> <span class="type">Cstubs_internals</span>.<span class="type">Int</span>, <span class="string">&quot;SOCK_STREAM&quot;</span> -&gt;</span><br><span class="line"> <span class="number">1</span></span><br><span class="line"> | <span class="type">Ctypes_static</span>.<span class="type">Primitive</span> <span class="type">Cstubs_internals</span>.<span class="type">Int</span>, <span class="string">&quot;SOCK_DGRAM&quot;</span> -&gt;</span><br><span class="line"> <span class="number">2</span></span><br><span class="line"> | <span class="type">Ctypes_static</span>.<span class="type">Primitive</span> <span class="type">Cstubs_internals</span>.<span class="type">Int</span>, <span class="string">&quot;SA_FAMILY_LEN&quot;</span> -&gt;</span><br><span class="line"> <span class="number">1</span></span><br><span class="line"> | <span class="type">Ctypes_static</span>.<span class="type">Primitive</span> <span class="type">Cstubs_internals</span>.<span class="type">Int</span>, <span class="string">&quot;SA_DATA_LEN&quot;</span> -&gt;</span><br><span class="line"> <span class="number">14</span></span><br><span class="line"> | <span class="type">Ctypes_static</span>.<span class="type">Primitive</span> <span class="type">Cstubs_internals</span>.<span class="type">Int</span>, <span class="string">&quot;AF_UNSPEC&quot;</span> -&gt;</span><br><span class="line"> <span class="number">0</span></span><br><span class="line"> | <span class="type">Ctypes_static</span>.<span class="type">Primitive</span> <span class="type">Cstubs_internals</span>.<span class="type">Int</span>, <span class="string">&quot;AF_UNIX&quot;</span> -&gt;</span><br><span class="line"> <span class="number">1</span></span><br><span class="line"> | <span class="type">Ctypes_static</span>.<span class="type">Primitive</span> <span class="type">Cstubs_internals</span>.<span class="type">Int</span>, <span class="string">&quot;AF_INET6&quot;</span> -&gt;</span><br><span class="line"> <span class="number">30</span></span><br><span class="line"> | <span class="type">Ctypes_static</span>.<span class="type">Primitive</span> <span class="type">Cstubs_internals</span>.<span class="type">Int</span>, <span class="string">&quot;AF_INET&quot;</span> -&gt;</span><br><span class="line"> <span class="number">2</span></span><br><span class="line"> | _, s -&gt; failwith (<span class="string">&quot;unmatched constant: &quot;</span>^ s)</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> enum (<span class="keyword">type</span> a) name ?typedef ?unexpected (alist : (a * <span class="built_in">int64</span>) <span class="built_in">list</span>) =</span><br><span class="line"> <span class="keyword">match</span> name <span class="keyword">with</span></span><br><span class="line"> | s -&gt;</span><br><span class="line"> failwith (<span class="string">&quot;unmatched enum: &quot;</span>^ s)</span><br></pre></td></tr></table></figure>
-
<p>The files used to describe how to build this binary using <code>dune</code> are located in a separate <code>[generator](https://github.com/toots/ocaml-sys-socket/tree/master/src/sys-socket/generator)</code> directory. Here’s the entry to build this one:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">(executable</span><br><span class="line"> (name gen_constants_c)</span><br><span class="line"> (modules gen_constants_c)</span><br><span class="line"> (libraries sys-socket.constants ctypes.stubs))</span><br><span class="line"></span><br><span class="line">(rule</span><br><span class="line"> (targets gen_constants.c)</span><br><span class="line"> (deps (:gen ./gen_constants_c.exe))</span><br><span class="line"> (action (run %&#123;gen&#125; %&#123;targets&#125;)))</span><br><span class="line"></span><br><span class="line">(rule</span><br><span class="line"> (targets gen_constants_c)</span><br><span class="line"> (deps (:c_code ./gen_constants.c))</span><br><span class="line"> (action (run %&#123;ocaml-config:c_compiler&#125; -I %&#123;lib:ctypes:&#125; -I %&#123;ocaml-config:standard_library&#125; -o %&#123;targets&#125; %&#123;c_code&#125;)))</span><br></pre></td></tr></table></figure>
-
<p>This executable is compiled during the next phase. Let’s move into it now!</p>
-<h2 id="Types-pass"><a href="#Types-pass" class="headerlink" title="Types pass"></a>Types pass</h2><p>During that phase, we use the constants exported during the previous phase to describe the various C structures and types. This is by far the most complex part of the code, making use of first-class modules and several OCaml tricks.</p>
+<h2 id="Types-pass"><a class="header-anchor" href="#Types-pass">¶</a>Types pass</h2>
+<p>During that phase, we use the constants exported during the previous phase to describe the various C structures and types. This is by far the most complex part of the code, making use of first-class modules and several OCaml tricks.</p>
<p>First, let’s look at how we tell <code>dune</code> that we need to generate the <code>.ml</code> file exporting our required constants from the previous pass:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">(rule</span><br><span class="line"> (targets sys_socket_generated_constants.ml)</span><br><span class="line"> (deps (:exec ../generator/exec.sh)</span><br><span class="line"> (:gen ../generator/gen_constants_c))</span><br><span class="line"> (action (with-stdout-to %&#123;targets&#125;</span><br><span class="line"> (system &quot;%&#123;exec&#125; %&#123;ocaml-config:system&#125; %&#123;gen&#125;&quot;))))</span><br></pre></td></tr></table></figure>
-
<p>With only this information, if the code refers to a <code>Sys_socket_generated_constants</code> module, <code>dune</code> will know that this module needs to be generated and how to do it. We will explain later the use of the <code>exec.sh</code> wrapper here.</p>
<p>Now that we can make use of the exported constants in our OCaml code, let’s see how we define the <code>Socklen</code> module, exporting abstract types and interface to use <code>socklen_t</code> integers:</p>
<figure class="highlight ocaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">module</span> <span class="type">Constants</span> = <span class="type">Sys_socket_constants</span>.<span class="type">Def</span>(<span class="type">Sys_socket_generated_constants</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">module</span> <span class="keyword">type</span> <span class="type">Socklen</span> = <span class="keyword">functor</span> (<span class="type">S</span> : <span class="type">Cstubs</span>.<span class="type">Types</span>.<span class="type">TYPE</span>) -&gt; <span class="keyword">sig</span></span><br><span class="line"> <span class="keyword">type</span> socklen</span><br><span class="line"> <span class="keyword">val</span> socklen_t : socklen <span class="type">S</span>.typ</span><br><span class="line"> <span class="keyword">val</span> int_of_socklen : socklen -&gt; <span class="built_in">int</span></span><br><span class="line"> <span class="keyword">val</span> socklen_of_int : <span class="built_in">int</span> -&gt; socklen</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> socklen : (<span class="keyword">module</span> <span class="type">Socklen</span>) =</span><br><span class="line"> <span class="keyword">match</span> <span class="type">Constants</span>.socklen_t_len <span class="keyword">with</span></span><br><span class="line"> | <span class="number">4</span> -&gt; (<span class="keyword">module</span> <span class="keyword">functor</span> (<span class="type">S</span> : <span class="type">Cstubs</span>.<span class="type">Types</span>.<span class="type">TYPE</span>) -&gt; <span class="keyword">struct</span></span><br><span class="line"> <span class="keyword">type</span> socklen = <span class="type">Unsigned</span>.uint32</span><br><span class="line"> <span class="keyword">let</span> socklen_t = <span class="type">S</span>.uint32_t</span><br><span class="line"> <span class="keyword">let</span> int_of_socklen = <span class="type">Unsigned</span>.<span class="type">UInt32</span>.to_int</span><br><span class="line"> <span class="keyword">let</span> socklen_of_int = <span class="type">Unsigned</span>.<span class="type">UInt32</span>.of_int</span><br><span class="line"> <span class="keyword">end</span>)</span><br><span class="line"> | <span class="number">8</span> -&gt; (<span class="keyword">module</span> <span class="keyword">functor</span> (<span class="type">S</span> : <span class="type">Cstubs</span>.<span class="type">Types</span>.<span class="type">TYPE</span>) -&gt; <span class="keyword">struct</span></span><br><span class="line"> <span class="keyword">type</span> socklen = <span class="type">Unsigned</span>.uint64</span><br><span class="line"> <span class="keyword">let</span> socklen_t = <span class="type">S</span>.uint64_t</span><br><span class="line"> <span class="keyword">let</span> int_of_socklen = <span class="type">Unsigned</span>.<span class="type">UInt64</span>.to_int</span><br><span class="line"> <span class="keyword">let</span> socklen_of_int = <span class="type">Unsigned</span>.<span class="type">UInt64</span>.of_int</span><br><span class="line"> <span class="keyword">end</span>)</span><br><span class="line"> | _ -&gt; <span class="keyword">assert</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">module</span> <span class="type">Socklen</span> = (<span class="keyword">val</span> socklen : <span class="type">Socklen</span>)</span><br></pre></td></tr></table></figure>
-
<p>As you can see, we make use of first-order modules and the size of the <code>socklen_t</code> integer to define the right API for the compiling host. Now let’s see how we define the <code>sockaddr</code> interface:</p>
<figure class="highlight ocaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">module</span> <span class="keyword">type</span> <span class="type">SaFamily</span> = <span class="keyword">sig</span></span><br><span class="line"> <span class="keyword">type</span> sa_family</span><br><span class="line"> <span class="keyword">val</span> int_of_sa_family : sa_family -&gt; <span class="built_in">int</span></span><br><span class="line"> <span class="keyword">val</span> sa_family_of_int : <span class="built_in">int</span> -&gt; sa_family</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">module</span> <span class="type">T</span> : <span class="keyword">functor</span> (<span class="type">S</span> : <span class="type">Cstubs</span>.<span class="type">Types</span>.<span class="type">TYPE</span>) -&gt; <span class="keyword">sig</span></span><br><span class="line"> <span class="keyword">val</span> t : sa_family <span class="type">S</span>.typ</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> saFamily : (<span class="keyword">module</span> <span class="type">SaFamily</span>) =</span><br><span class="line"> <span class="keyword">match</span> <span class="type">Constants</span>.sa_family_len <span class="keyword">with</span></span><br><span class="line"> | <span class="number">1</span> -&gt; (<span class="keyword">module</span> <span class="keyword">struct</span></span><br><span class="line"> <span class="keyword">type</span> sa_family = <span class="type">Unsigned</span>.uint8</span><br><span class="line"> <span class="keyword">let</span> int_of_sa_family = <span class="type">Unsigned</span>.<span class="type">UInt8</span>.to_int</span><br><span class="line"> <span class="keyword">let</span> sa_family_of_int = <span class="type">Unsigned</span>.<span class="type">UInt8</span>.of_int </span><br><span class="line"> <span class="keyword">module</span> <span class="type">T</span> (<span class="type">S</span> : <span class="type">Cstubs</span>.<span class="type">Types</span>.<span class="type">TYPE</span>) = <span class="keyword">struct</span></span><br><span class="line"> <span class="keyword">let</span> t = <span class="type">S</span>.uint8_t</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> <span class="keyword">end</span>)</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="keyword">module</span> <span class="type">SaFamily</span> = (<span class="keyword">val</span> saFamily : <span class="type">SaFamily</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">module</span> <span class="type">Def</span> (<span class="type">S</span> : <span class="type">Cstubs</span>.<span class="type">Types</span>.<span class="type">TYPE</span>) = <span class="keyword">struct</span></span><br><span class="line"> <span class="keyword">include</span> <span class="type">Constants</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">include</span> <span class="type">Socklen</span>(<span class="type">S</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">include</span> <span class="type">SaFamily</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">module</span> <span class="type">SaFamilyT</span> = <span class="type">SaFamily</span>.<span class="type">T</span>(<span class="type">S</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> sa_family_t = <span class="type">S</span>.typedef <span class="type">SaFamilyT</span>.t <span class="string">&quot;sa_family_t&quot;</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">module</span> <span class="type">Sockaddr</span> = <span class="keyword">struct</span></span><br><span class="line"> <span class="keyword">type</span> t = <span class="built_in">unit</span></span><br><span class="line"> <span class="keyword">let</span> t = <span class="type">S</span>.structure <span class="string">&quot;sockaddr&quot;</span></span><br><span class="line"> <span class="keyword">let</span> sa_family = <span class="type">S</span>.field t <span class="string">&quot;sa_family&quot;</span> sa_family_t</span><br><span class="line"> <span class="keyword">let</span> sa_data = <span class="type">S</span>.field t <span class="string">&quot;sa_data&quot;</span> (<span class="type">S</span>.<span class="built_in">array</span> sa_data_len <span class="type">S</span>.<span class="built_in">char</span>)</span><br><span class="line"> <span class="keyword">let</span> <span class="literal">()</span> = <span class="type">S</span>.seal t</span><br><span class="line"> <span class="keyword">end</span></span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
-
<p>Here, too, we make use of the size of <code>sa_family</code> as exported previously to define the right structure fields.</p>
<p>Next step, we need to compile this interface again to export the right offset for the various structures that have been defined. That’s <code>dune</code>’s job again!</p>
<p>First, the generator code:</p>
<figure class="highlight ocaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> c_headers = <span class="string">&quot;</span></span><br><span class="line"><span class="string">#ifdef _WIN32</span></span><br><span class="line"><span class="string"> #include &lt;winsock2.h&gt;</span></span><br><span class="line"><span class="string"> #include &lt;ws2tcpip.h&gt;</span></span><br><span class="line"><span class="string">#else</span></span><br><span class="line"><span class="string"> #include &lt;sys/socket.h&gt;</span></span><br><span class="line"><span class="string"> #include &lt;sys/un.h&gt;</span></span><br><span class="line"><span class="string"> #include &lt;netinet/in.h&gt;</span></span><br><span class="line"><span class="string"> #include &lt;netdb.h&gt;</span></span><br><span class="line"><span class="string">#endif</span></span><br><span class="line"><span class="string">&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="literal">()</span> =</span><br><span class="line"> <span class="keyword">let</span> fname = <span class="type">Sys</span>.argv.(<span class="number">1</span>) <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">let</span> oc = open_out_bin fname <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">let</span> format =</span><br><span class="line"> <span class="type">Format</span>.formatter_of_out_channel oc</span><br><span class="line"> <span class="keyword">in</span></span><br><span class="line"> <span class="type">Format</span>.fprintf format <span class="string">&quot;%s@\n&quot;</span> c_headers;</span><br><span class="line"> <span class="type">Cstubs</span>.<span class="type">Types</span>.write_c format (<span class="keyword">module</span> <span class="type">Sys_socket_types</span>.<span class="type">Def</span>);</span><br><span class="line"> <span class="type">Format</span>.pp_print_flush format <span class="literal">()</span>;</span><br><span class="line"> close_out oc</span><br></pre></td></tr></table></figure>
-
<p>And the build instructions:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">(executable</span><br><span class="line"> (name gen_types_c)</span><br><span class="line"> (modules gen_types_c)</span><br><span class="line"> (libraries sys-socket.types ctypes.stubs))</span><br><span class="line"></span><br><span class="line">(rule</span><br><span class="line"> (targets gen_types.c)</span><br><span class="line"> (deps (:gen ./gen_types_c.exe))</span><br><span class="line"> (action (run %&#123;gen&#125; %&#123;targets&#125;)))</span><br><span class="line"></span><br><span class="line">(rule</span><br><span class="line"> (targets gen_types_c)</span><br><span class="line"> (deps (:c_code ./gen_types.c))</span><br><span class="line"> (action (run %&#123;ocaml-config:c_compiler&#125; -I %&#123;lib:ctypes:&#125; -I %&#123;ocaml-config:standard_library&#125; -o %&#123;targets&#125; %&#123;c_code&#125;)))</span><br></pre></td></tr></table></figure>
-
<p>Once, compiled, the exported <code>.ml</code> looks like this:</p>
<figure class="highlight ocaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">include</span> <span class="type">Ctypes</span></span><br><span class="line"><span class="keyword">let</span> lift x = x</span><br><span class="line"><span class="keyword">open</span> <span class="type">Ctypes_static</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="keyword">rec</span> field : <span class="keyword">type</span> t a. t typ -&gt; <span class="built_in">string</span> -&gt; a typ -&gt; (a, t) field =</span><br><span class="line"> <span class="keyword">fun</span> s fname ftype -&gt; <span class="keyword">match</span> s, fname <span class="keyword">with</span></span><br><span class="line">...</span><br><span class="line"> | <span class="type">Struct</span> (&#123; tag = <span class="string">&quot;sockaddr&quot;</span>&#125; <span class="keyword">as</span> s&#x27;), <span class="string">&quot;sa_data&quot;</span> -&gt;</span><br><span class="line"> <span class="keyword">let</span> f = &#123;ftype; fname; foffset = <span class="number">2</span>&#125; <span class="keyword">in</span></span><br><span class="line"> (s&#x27;.fields &lt;- <span class="type">BoxedField</span> f :: s&#x27;.fields; f)</span><br><span class="line"> | <span class="type">Struct</span> (&#123; tag = <span class="string">&quot;sockaddr&quot;</span>&#125; <span class="keyword">as</span> s&#x27;), <span class="string">&quot;sa_family&quot;</span> -&gt;</span><br><span class="line"> <span class="keyword">let</span> f = &#123;ftype; fname; foffset = <span class="number">1</span>&#125; <span class="keyword">in</span></span><br><span class="line"> (s&#x27;.fields &lt;- <span class="type">BoxedField</span> f :: s&#x27;.fields; f)</span><br><span class="line"> | <span class="type">View</span> &#123; ty &#125;, _ -&gt;</span><br><span class="line"> <span class="keyword">let</span> &#123; ftype; foffset; fname &#125; = field ty fname ftype <span class="keyword">in</span></span><br><span class="line"> &#123; ftype; foffset; fname &#125;</span><br><span class="line"> | _ -&gt; failwith (<span class="string">&quot;Unexpected field &quot;</span>^ fname)</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="keyword">rec</span> seal : <span class="keyword">type</span> a. a typ -&gt; <span class="built_in">unit</span> = <span class="keyword">function</span></span><br><span class="line">...</span><br><span class="line"> | <span class="type">Struct</span> (&#123; tag = <span class="string">&quot;sockaddr_storage&quot;</span>; spec = <span class="type">Incomplete</span> _ &#125; <span class="keyword">as</span> s&#x27;) -&gt;</span><br><span class="line"> s&#x27;.spec &lt;- <span class="type">Complete</span> &#123; size = <span class="number">128</span>; align = <span class="number">8</span> &#125;</span><br><span class="line"> | <span class="type">Struct</span> (&#123; tag = <span class="string">&quot;sockaddr&quot;</span>; spec = <span class="type">Incomplete</span> _ &#125; <span class="keyword">as</span> s&#x27;) -&gt;</span><br><span class="line"> s&#x27;.spec &lt;- <span class="type">Complete</span> &#123; size = <span class="number">16</span>; align = <span class="number">1</span> &#125;</span><br><span class="line"> | <span class="type">Struct</span> &#123; tag; spec = <span class="type">Complete</span> _ &#125; -&gt;</span><br><span class="line"> raise (<span class="type">ModifyingSealedType</span> tag)</span><br><span class="line"> | <span class="type">Union</span> &#123; utag; uspec = <span class="type">Some</span> _ &#125; -&gt;</span><br><span class="line"> raise (<span class="type">ModifyingSealedType</span> utag)</span><br><span class="line"> | <span class="type">View</span> &#123; ty &#125; -&gt; seal ty</span><br><span class="line"> | _ -&gt;</span><br><span class="line"> raise (<span class="type">Unsupported</span> <span class="string">&quot;Sealing a non-structured type&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="symbol">&#x27;a</span> const = <span class="symbol">&#x27;a</span></span><br><span class="line"><span class="keyword">let</span> constant (<span class="keyword">type</span> t) name (t : t typ) : t = <span class="keyword">match</span> t, name <span class="keyword">with</span></span><br><span class="line"> | _, s -&gt; failwith (<span class="string">&quot;unmatched constant: &quot;</span>^ s)</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> enum (<span class="keyword">type</span> a) name ?typedef ?unexpected (alist : (a * <span class="built_in">int64</span>) <span class="built_in">list</span>) =</span><br><span class="line"> <span class="keyword">match</span> name <span class="keyword">with</span></span><br><span class="line"> | s -&gt;</span><br><span class="line"> failwith (<span class="string">&quot;unmatched enum: &quot;</span>^ s)</span><br></pre></td></tr></table></figure>
-
<p>As you can see, this exports all the offsets required to access the fields inside a <code>sockaddr_t</code> structure. We’re now ready to move to the final stage, which is the actual binding stubs!</p>
-<h2 id="Binding-stubs"><a href="#Binding-stubs" class="headerlink" title="Binding stubs"></a>Binding stubs</h2><p>First step in this pass, just like with the previous ones, we need to configure <code>dune</code> to be able to build the exported <code>.ml</code> code from the <code>types</code> pass:</p>
+<h2 id="Binding-stubs"><a class="header-anchor" href="#Binding-stubs">¶</a>Binding stubs</h2>
+<p>First step in this pass, just like with the previous ones, we need to configure <code>dune</code> to be able to build the exported <code>.ml</code> code from the <code>types</code> pass:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">(rule</span><br><span class="line"> (targets sys_socket_generated_types.ml)</span><br><span class="line"> (deps (:exec ../generator/exec.sh)</span><br><span class="line"> (:gen ../generator/gen_types_c))</span><br><span class="line"> (action (with-stdout-to %&#123;targets&#125;</span><br><span class="line"> (system &quot;%&#123;exec&#125; %&#123;ocaml-config:system&#125; %&#123;gen&#125;&quot;))))</span><br></pre></td></tr></table></figure>
-
<p>And we can now define the proper bindings. Here’s how it looks like:</p>
<figure class="highlight ocaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">open</span> <span class="type">Ctypes</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">module</span> <span class="type">Def</span> (<span class="type">F</span> : <span class="type">Cstubs</span>.<span class="type">FOREIGN</span>) = <span class="keyword">struct</span></span><br><span class="line"> <span class="keyword">open</span> <span class="type">F</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">module</span> <span class="type">Types</span> = <span class="type">Sys_socket_types</span>.<span class="type">Def</span>(<span class="type">Sys_socket_generated_types</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">open</span> <span class="type">Types</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> getnameinfo = foreign <span class="string">&quot;getnameinfo&quot;</span> (ptr sockaddr_t @-&gt; socklen_t @-&gt; ptr <span class="built_in">char</span> @-&gt; socklen_t @-&gt; ptr <span class="built_in">char</span> @-&gt; socklen_t @-&gt; <span class="built_in">int</span> @-&gt; (returning <span class="built_in">int</span>))</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
-
-<p>As you can see, we’re exporting the <code>getnameinfo</code> function, taking various arguments, including a pointer to a <code>sockaddr_t</code> structure and a couple of <code>socklen_t</code> integers, making use of all the various data types and structures previously defined. The exact specifications of this function can be found <a target="_blank" rel="noopener" href="https://pubs.opengroup.org/onlinepubs/009695399/functions/getnameinfo.html">here</a>. We can now define out top-level API..</p>
-<h2 id="Final-API"><a href="#Final-API" class="headerlink" title="Final API"></a>Final API</h2><p>Building upon the previous modules, we export various OCaml idiomatic APIs that the binding user can now use to build new bindings against the <code>socket.h</code> APIs.</p>
+<p>As you can see, we’re exporting the <code>getnameinfo</code> function, taking various arguments, including a pointer to a <code>sockaddr_t</code> structure and a couple of <code>socklen_t</code> integers, making use of all the various data types and structures previously defined. The exact specifications of this function can be found <a target="_blank" rel="noopener" href="https://pubs.opengroup.org/onlinepubs/009695399/functions/getnameinfo.html">here</a>. We can now define out top-level API…</p>
+<h2 id="Final-API"><a class="header-anchor" href="#Final-API">¶</a>Final API</h2>
+<p>Building upon the previous modules, we export various OCaml idiomatic APIs that the binding user can now use to build new bindings against the <code>socket.h</code> APIs.</p>
<p>Just like with the previous steps, first we need to configure the build system:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">(rule</span><br><span class="line"> (targets sys_socket_generated_stubs.ml)</span><br><span class="line"> (deps (:gen ./generator/gen_stubs.exe))</span><br><span class="line"> (action (run %&#123;gen&#125; ml %&#123;targets&#125;)))</span><br><span class="line"></span><br><span class="line">(rule</span><br><span class="line"> (targets sys_socket_generated_stubs.c)</span><br><span class="line"> (deps (:gen ./generator/gen_stubs.exe))</span><br><span class="line"> (action (run %&#123;gen&#125; c %&#123;targets&#125;)))</span><br></pre></td></tr></table></figure>
-
<p>This time, we need <code>ocaml-ctypes</code> to generate two compilation units: a <code>.ml</code> file describing the API exported during the <code>stubs</code> phase, as well as the C code to glue it with the C APIs. Here’s the code for that generator:</p>
<figure class="highlight ocaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> c_headers = <span class="string">&quot;</span></span><br><span class="line"><span class="string">#ifdef _WIN32</span></span><br><span class="line"><span class="string"> #include &lt;winsock2.h&gt;</span></span><br><span class="line"><span class="string"> #include &lt;ws2tcpip.h&gt;</span></span><br><span class="line"><span class="string">#else</span></span><br><span class="line"><span class="string"> #include &lt;sys/socket.h&gt;</span></span><br><span class="line"><span class="string"> #include &lt;netinet/in.h&gt;</span></span><br><span class="line"><span class="string"> #include &lt;arpa/inet.h&gt;</span></span><br><span class="line"><span class="string"> #include &lt;netdb.h&gt;</span></span><br><span class="line"><span class="string">#endif</span></span><br><span class="line"><span class="string">#include &lt;string.h&gt;</span></span><br><span class="line"><span class="string">&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="literal">()</span> =</span><br><span class="line"> <span class="keyword">let</span> mode = <span class="type">Sys</span>.argv.(<span class="number">1</span>) <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">let</span> fname = <span class="type">Sys</span>.argv.(<span class="number">2</span>) <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">let</span> oc = open_out_bin fname <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">let</span> format =</span><br><span class="line"> <span class="type">Format</span>.formatter_of_out_channel oc</span><br><span class="line"> <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">let</span> fn =</span><br><span class="line"> <span class="keyword">match</span> mode <span class="keyword">with</span></span><br><span class="line"> | <span class="string">&quot;ml&quot;</span> -&gt; <span class="type">Cstubs</span>.write_ml</span><br><span class="line"> | <span class="string">&quot;c&quot;</span> -&gt;</span><br><span class="line"> <span class="type">Format</span>.fprintf format <span class="string">&quot;%s@\n&quot;</span> c_headers;</span><br><span class="line"> <span class="type">Cstubs</span>.write_c</span><br><span class="line"> | _ -&gt; <span class="keyword">assert</span> <span class="literal">false</span></span><br><span class="line"> <span class="keyword">in</span></span><br><span class="line"> fn ~concurrency:<span class="type">Cstubs</span>.unlocked format ~prefix:<span class="string">&quot;sys_socket&quot;</span> (<span class="keyword">module</span> <span class="type">Sys_socket_stubs</span>.<span class="type">Def</span>);</span><br><span class="line"> <span class="type">Format</span>.pp_print_flush format <span class="literal">()</span>;</span><br><span class="line"> close_out oc</span><br></pre></td></tr></table></figure>
-
<p>The exported <code>.ml</code> and <code>.c</code> files are omitted here for simplicity but the reader can generated them themselves from the <code>[ocaml-sys-socket](https://github.com/toots/ocaml-sys-socket)</code> repository if they are curious about their actual content.</p>
<p>We can now export our top-level API:</p>
<figure class="highlight ocaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">open</span> <span class="type">Ctypes</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">include</span> <span class="type">Sys_socket_types</span>.<span class="type">SaFamily</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">include</span> <span class="type">Sys_socket_stubs</span>.<span class="type">Def</span>(<span class="type">Sys_socket_generated_stubs</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> socklen = <span class="type">Types</span>.socklen</span><br><span class="line"><span class="keyword">let</span> socklen_t = <span class="type">Types</span>.socklen_t</span><br><span class="line"><span class="keyword">let</span> int_of_socklen = <span class="type">Types</span>.int_of_socklen</span><br><span class="line"><span class="keyword">let</span> socklen_of_int = <span class="type">Types</span>.socklen_of_int</span><br><span class="line"></span><br><span class="line"><span class="keyword">module</span> <span class="type">Sockaddr</span> = <span class="keyword">struct</span></span><br><span class="line"> <span class="keyword">include</span> <span class="type">Types</span>.<span class="type">Sockaddr</span></span><br><span class="line"> <span class="keyword">let</span> from_sockaddr_storage = from_sockaddr_storage t</span><br><span class="line"> <span class="keyword">let</span> sa_data_len = <span class="type">Types</span>.sa_data_len</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> getnameinfo sockaddr_ptr =</span><br><span class="line"> <span class="keyword">let</span> maxhost = <span class="type">Types</span>.ni_maxhost <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">let</span> s = allocate_n <span class="built_in">char</span> ~count:maxhost <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">let</span> maxserv = <span class="type">Types</span>.ni_maxserv <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">let</span> p = allocate_n <span class="built_in">char</span> ~count:maxserv <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">match</span> getnameinfo sockaddr_ptr (socklen_of_int (sizeof sockaddr_t))</span><br><span class="line"> s (socklen_of_int maxhost) </span><br><span class="line"> p (socklen_of_int maxserv)</span><br><span class="line"> (<span class="type">Types</span>.ni_numerichost <span class="keyword">lor</span></span><br><span class="line"> <span class="type">Types</span>.ni_numericserv) <span class="keyword">with</span></span><br><span class="line"> | <span class="number">0</span> -&gt;</span><br><span class="line"> <span class="keyword">let</span> host =</span><br><span class="line"> <span class="keyword">let</span> length =</span><br><span class="line"> <span class="type">Unsigned</span>.<span class="type">Size_t</span>.to_int</span><br><span class="line"> (strnlen s (<span class="type">Unsigned</span>.<span class="type">Size_t</span>.of_int maxhost))</span><br><span class="line"> <span class="keyword">in</span></span><br><span class="line"> string_from_ptr s ~length</span><br><span class="line"> <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">let</span> port =</span><br><span class="line"> <span class="keyword">let</span> length =</span><br><span class="line"> <span class="type">Unsigned</span>.<span class="type">Size_t</span>.to_int</span><br><span class="line"> (strnlen p (<span class="type">Unsigned</span>.<span class="type">Size_t</span>.of_int maxserv))</span><br><span class="line"> <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">let</span> port =</span><br><span class="line"> string_from_ptr p ~length</span><br><span class="line"> <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">try</span></span><br><span class="line"> int_of_string port</span><br><span class="line"> <span class="keyword">with</span> _ -&gt;</span><br><span class="line"> <span class="keyword">match</span> getservbyname p null <span class="keyword">with</span></span><br><span class="line"> | ptr <span class="keyword">when</span> is_null ptr -&gt; failwith <span class="string">&quot;getnameinfo&quot;</span></span><br><span class="line"> | ptr -&gt;</span><br><span class="line"> <span class="type">Unsigned</span>.<span class="type">UInt16</span>.to_int</span><br><span class="line"> (ntohs (!@ (ptr |-&gt; <span class="type">Types</span>.<span class="type">Servent</span>.s_port)))</span><br><span class="line"> <span class="keyword">in</span></span><br><span class="line"> host, port</span><br><span class="line"> | _ -&gt; failwith <span class="string">&quot;getnameinfo&quot;</span></span><br><span class="line"></span><br><span class="line">...</span><br></pre></td></tr></table></figure>
-
<figure class="highlight ocaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">open</span> <span class="type">Ctypes</span></span><br><span class="line"></span><br><span class="line"><span class="comment">(** Ctypes routines for C type socklen_t. *)</span></span><br><span class="line"><span class="keyword">type</span> socklen</span><br><span class="line"><span class="keyword">val</span> socklen_t : socklen typ</span><br><span class="line"><span class="keyword">val</span> int_of_socklen : socklen -&gt; <span class="built_in">int</span></span><br><span class="line"><span class="keyword">val</span> socklen_of_int : <span class="built_in">int</span> -&gt; socklen</span><br><span class="line"></span><br><span class="line"><span class="comment">(** Generic sockaddr_t structure. *)</span></span><br><span class="line"><span class="keyword">module</span> <span class="type">Sockaddr</span> : <span class="keyword">sig</span></span><br><span class="line"> <span class="keyword">type</span> t</span><br><span class="line"> <span class="keyword">val</span> t : t structure typ</span><br><span class="line"> <span class="keyword">val</span> sa_family : (sa_family, t structure) field</span><br><span class="line"> <span class="keyword">val</span> sa_data : (<span class="built_in">char</span> carray, t structure) field</span><br><span class="line"> <span class="keyword">val</span> sa_data_len : <span class="built_in">int</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> from_sockaddr_storage : <span class="type">SockaddrStorage</span>.t structure ptr -&gt; t structure ptr</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">(** IP address conversion functions. *)</span></span><br><span class="line"><span class="keyword">val</span> getnameinfo : sockaddr ptr -&gt; <span class="built_in">string</span> * <span class="built_in">int</span></span><br><span class="line"></span><br><span class="line">...</span><br></pre></td></tr></table></figure>
-
<p>That’s it! We now have <code>ocaml-ctypes</code> specific data types and structures that can be used to interface with the host’s native <code>socket.h</code> APIs. Note that we also worked on top of the original low-level binding to <code>getnameinfo</code> to export a higher-level function more idiomatic to the OCaml language.</p>
-<h1 id="Lagniappe-cross-compilation-to-Windows"><a href="#Lagniappe-cross-compilation-to-Windows" class="headerlink" title="Lagniappe: cross-compilation to Windows"></a>Lagniappe: cross-compilation to Windows</h1><p>On windows platforms, <code>liquidsoap</code> is compiled using <code>[ocaml-cross-windows](https://github.com/ocaml-cross/opam-cross-windows)</code> and, since windows does have compatible socket APIs, we wanted to also look at cross-compiling for the windows target, which is where we hit a snag on the current <code>dune</code> support.</p>
+<h1>Lagniappe: cross-compilation to Windows</h1>
+<p>On windows platforms, <code>liquidsoap</code> is compiled using <code>[ocaml-cross-windows](https://github.com/ocaml-cross/opam-cross-windows)</code> and, since windows does have compatible socket APIs, we wanted to also look at cross-compiling for the windows target, which is where we hit a snag on the current <code>dune</code> support.</p>
<p>The problem is that, at each intermediary steps, in the case of a cross-compilation, the compiled binaries need to use the target’s OS headers and not the host’s headers, otherwise we end up using offsets specific to e.g. Debian but for a windows binary.</p>
<p>In this case, this means that the compiled <code>.exe</code> binaries need to be windows binaries and that we need to execute them as windows native binaries, using <code>[wine](https://www.winehq.org/)</code> .</p>
<p><code>dune</code> has a truly amazing <a target="_blank" rel="noopener" href="https://dune.readthedocs.io/en/latest/cross-compilation.html">support for cross-compiling</a>, which we do not cover here, but, unfortunately, its primitives for building and executing binaries do not yet cover this use case. Thus we had to trick it into compiling things the way we wanted to do, which why we are using the <code>exec.sh</code> wrapper. Here’s its code:</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/sh</span></span><br><span class="line"></span><br><span class="line">SYSTEM=<span class="variable">$1</span></span><br><span class="line">CMD=<span class="variable">$2</span></span><br><span class="line">ARG=<span class="variable">$3</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">test</span> <span class="string">&quot;<span class="variable">$&#123;SYSTEM&#125;</span>&quot;</span> = <span class="string">&quot;mingw&quot;</span>; <span class="keyword">then</span></span><br><span class="line"> wine <span class="variable">$CMD</span> <span class="variable">$ARG</span></span><br><span class="line"><span class="keyword">elif</span> <span class="built_in">test</span> <span class="string">&quot;<span class="variable">$&#123;SYSTEM&#125;</span>&quot;</span> = <span class="string">&quot;mingw64&quot;</span>; <span class="keyword">then</span></span><br><span class="line"> wine64 <span class="variable">$CMD</span> <span class="variable">$ARG</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line"> <span class="variable">$CMD</span> <span class="variable">$ARG</span></span><br></pre></td></tr></table></figure>
-
<p>Now, you can go back to the previous <code>dune</code> files and see how this wrapper allows to execute binaries according to the system that the corresponding <code>ocamlopt</code> compiler has been configured to build for.</p>
-<h1 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h1><p>It’s been a fun time working on this binding! It’s amazing to see the level of details that can be built through <code>ocaml-ctypes</code> using their provided primitives. Ultimately, the binding is very clean and elegant, with very few low-level assumptions.</p>
+<h1>Conclusion</h1>
+<p>It’s been a fun time working on this binding! It’s amazing to see the level of details that can be built through <code>ocaml-ctypes</code> using their provided primitives. Ultimately, the binding is very clean and elegant, with very few low-level assumptions.</p>
<p>Likewise, the simplicity and power of the <code>dune</code> build system makes this very fluid to build. Without it, each of the described steps above would have been much more painful to execute and compile.</p>
-<p>[1]: My bet is that, at the time the POSIX specifications were being written, there we already several inconsistent <code>socket.h</code> headers out in the wild among the various historical UNIX flavors..</p>
+<p>[1]: My bet is that, at the time the POSIX specifications were being written, there we already several inconsistent <code>socket.h</code> headers out in the wild among the various historical UNIX flavors…</p>
</div>
diff --git a/2024/09/15/肩周炎和肩袖损伤的区别/index.html b/2024/09/15/肩周炎和肩袖损伤的区别/index.html
index 1c666b0b..16e591a2 100644
--- a/2024/09/15/肩周炎和肩袖损伤的区别/index.html
+++ b/2024/09/15/肩周炎和肩袖损伤的区别/index.html
@@ -204,15 +204,20 @@
<p>肩袖损伤是由退行性病变或外力等导致肩袖的4块肌肉、肌腱发生病变,进而导致肩关节局部疼痛、活动受限的疾病。</p>
<p>许多肩袖损伤患者无明确的外伤史,而是由长期做过顶运动、提重物或上肢长期固定于一个姿势引起的。</p>
<ul>
-<li><p>肩周炎的疼痛范围广,涉及整个肩关节,肩袖损伤引起的疼痛多出现在肩前方、外上方</p>
+<li>
+<p>肩周炎的疼痛范围广,涉及整个肩关节,肩袖损伤引起的疼痛多出现在肩前方、外上方</p>
</li>
-<li><p>肩周炎的压痛点多而广,肩袖损的伤压痛点多出现在肩前方、上方及肩胛骨外侧缘。</p>
+<li>
+<p>肩周炎的压痛点多而广,肩袖损的伤压痛点多出现在肩前方、上方及肩胛骨外侧缘。</p>
</li>
-<li><p>肩周炎患者对气候变化较为敏感,肩袖损伤患者对气候变化不敏感,对劳累、提重物及做过顶运动较为敏感。</p>
+<li>
+<p>肩周炎患者对气候变化较为敏感,肩袖损伤患者对气候变化不敏感,对劳累、提重物及做过顶运动较为敏感。</p>
</li>
-<li><p>肩周炎患者活动受限范围广,肩袖损伤患者多以肩关节外展活动受限为主,同时伴有外展无力。</p>
+<li>
+<p>肩周炎患者活动受限范围广,肩袖损伤患者多以肩关节外展活动受限为主,同时伴有外展无力。</p>
</li>
-<li><p>肩周炎患者进行上举过顶运动训练后活动范围会好转,症状会减轻;而肩袖损伤患者进行上举过顶运动训练后疼痛加重。</p>
+<li>
+<p>肩周炎患者进行上举过顶运动训练后活动范围会好转,症状会减轻;而肩袖损伤患者进行上举过顶运动训练后疼痛加重。</p>
</li>
</ul>
diff --git a/2024/09/27/Turborepo-简述/index.html b/2024/09/27/Turborepo-简述/index.html
index 91218d7a..505b2d46 100644
--- a/2024/09/27/Turborepo-简述/index.html
+++ b/2024/09/27/Turborepo-简述/index.html
@@ -192,23 +192,27 @@
</div>
</div>
<div class="post-content">
- <h2 id="Turborepo-简介"><a href="#Turborepo-简介" class="headerlink" title="Turborepo 简介"></a>Turborepo 简介</h2><p>Monorepos 有很多优势,但它们难以扩展。每个工作区都有自己的测试套件、自己的 linting 和构建过程。单个 monorepo 可能有数千个任务要执行。</p>
+ <h2 id="Turborepo-简介"><a class="header-anchor" href="#Turborepo-简介">¶</a>Turborepo 简介</h2>
+<p>Monorepos 有很多优势,但它们难以扩展。每个工作区都有自己的测试套件、自己的 linting 和构建过程。单个 monorepo 可能有数千个任务要执行。</p>
<p>Turborepo 是一个专为 JavaScript 和 TypeScript 代码库设计的构建系统,旨在优化 monorepos 和 single-package workspace 中的任务。它通过远程缓存(remote caching)和高效的任务调度(task scheduling)来解决 monorepos 中的扩展问题。Turborepo 也可以增量部署(adopted incrementally),并与各种包管理器配合使用。</p>
<ul>
<li>远程缓存: 存储所有任务的结果,CI 就不需要重复执行相同的工作了。</li>
<li>任务调度:利用所有核心的性能并行处理任务,尽可能的加速。</li>
</ul>
<p><code>turbo</code> 基于 workspace 构建,workspaces 是 JavaScript 生态系统中包管理器的一项功能,允许将多个包分组到一个存储库中:</p>
-<h3 id="Workspace"><a href="#Workspace" class="headerlink" title="Workspace"></a>Workspace</h3><p>在 JavaScript 中,Workspace 是指仓库中的特定实体,可以是<a target="_blank" rel="noopener" href="https://vercel.com/docs/vercel-platform/glossary#single-package-workspace">单个包</a>或<a target="_blank" rel="noopener" href="https://vercel.com/docs/vercel-platform/glossary#multi-package-workspace">包的集合</a>。<br>包管理器的 root lock 文件(例如 <code>pnpm-lock.yaml</code>)以及任何其他配置都位于 Wrokspace 的根目录。在 Monorepo 中可以有多个工作区,每个工作区位于存储库的子目录中。</p>
-<h3 id="Single-package-workspace"><a href="#Single-package-workspace" class="headerlink" title="Single-package workspace"></a><a target="_blank" rel="noopener" href="https://vercel.com/docs/vercel-platform/glossary#single-package-workspace">Single-package workspace</a></h3><p>只有一个独立包的工作区,在工作区根目录下有一个 <code>package.json</code> 文件。</p>
-<h3 id="Multi-package-workspace"><a href="#Multi-package-workspace" class="headerlink" title="Multi-package workspace"></a><a target="_blank" rel="noopener" href="https://vercel.com/docs/vercel-platform/glossary#multi-package-workspace">Multi-package workspace</a></h3><p>包含多个包的工作区,包含多个 <code>package.json</code> 文件,其中一个位于工作区根目录中用于全局配置,其他位于每个包目录中。</p>
+<h3 id="Workspace"><a class="header-anchor" href="#Workspace">¶</a>Workspace</h3>
+<p>在 JavaScript 中,Workspace 是指仓库中的特定实体,可以是<a target="_blank" rel="noopener" href="https://vercel.com/docs/vercel-platform/glossary#single-package-workspace">单个包</a>或<a target="_blank" rel="noopener" href="https://vercel.com/docs/vercel-platform/glossary#multi-package-workspace">包的集合</a>。<br>
+包管理器的 root lock 文件(例如 <code>pnpm-lock.yaml</code>)以及任何其他配置都位于 Wrokspace 的根目录。在 Monorepo 中可以有多个工作区,每个工作区位于存储库的子目录中。</p>
+<h3 id="Single-package-workspace"><a class="header-anchor" href="#Single-package-workspace">¶</a><a target="_blank" rel="noopener" href="https://vercel.com/docs/vercel-platform/glossary#single-package-workspace">Single-package workspace</a></h3>
+<p>只有一个独立包的工作区,在工作区根目录下有一个 <code>package.json</code> 文件。</p>
+<h3 id="Multi-package-workspace"><a class="header-anchor" href="#Multi-package-workspace">¶</a><a target="_blank" rel="noopener" href="https://vercel.com/docs/vercel-platform/glossary#multi-package-workspace">Multi-package workspace</a></h3>
+<p>包含多个包的工作区,包含多个 <code>package.json</code> 文件,其中一个位于工作区根目录中用于全局配置,其他位于每个包目录中。</p>
<blockquote>
<p>这种类型的工作区通常称为 monorepo</p>
</blockquote>
<hr>
<p>以 npm 为例,turbo 会初始化一个这样的目录结构使其成为有效的 workspace:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">|- package.json</span><br><span class="line">|- package-lock.json</span><br><span class="line">|- turbo.json</span><br><span class="line">|- apps</span><br><span class="line">|-- docs</span><br><span class="line">|--- package.json</span><br><span class="line">|-- web</span><br><span class="line">|--- package.json</span><br><span class="line">|- packages</span><br><span class="line">|--- ui</span><br></pre></td></tr></table></figure>
-
<p>一个 “有效的” turbo 项目至少要有:</p>
<ul>
<li>包管理器描述的包</li>
@@ -219,7 +223,6 @@
</ul>
<p>例如,在根目录的 <code>package.json</code> 中配置:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;workspaces&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"> <span class="string">&quot;apps/*&quot;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="string">&quot;packages/*&quot;</span></span><br><span class="line"> <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
<p>那么 <code>apps</code> 或 <code>packages</code> 目录中有 <code>package.json</code> 的每个目录都将被视为一个包。</p>
<blockquote>
<p>注意:Turborepo 不支持嵌套包,例如 <code>apps/</code> 或 <code>packages/</code> 这种,将一个包放在<code>apps/a</code> 并将另一个包放在 <code>apps/a/b</code> 的结构将导致错误。</p>
@@ -227,10 +230,10 @@
</blockquote>
<p>根目录的 <code>package.json</code> 是 workspace 的基础,常见的配置:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;private&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;turbo run build&quot;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;turbo run dev&quot;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;lint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;turbo run lint&quot;</span></span><br><span class="line"> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;devDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;turbo&quot;</span><span class="punctuation">:</span> <span class="string">&quot;latest&quot;</span></span><br><span class="line"> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;packageManager&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[email protected]&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
<p>而根目录的 <code>turbo.json</code> 用于配置 <code>turbo</code> 的行为。那些 lock 文件是包管理器和 <code>turbo</code> 用于 reproducible 的关键。此外,Turborepo 还利用它们分析工作区中<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/internal-packages">内部包</a>之间的依赖关系。</p>
<hr>
-<h3 id="包中的-package-json"><a href="#包中的-package-json" class="headerlink" title="包中的 package.json"></a>包中的 <code>package.json</code></h3><p><a target="_blank" rel="noopener" href="https://nodejs.org/api/packages.html#name"><code>name</code> 字段</a>用于标识包。它在 workspace 中应该是唯一的。</p>
+<h3 id="包中的-package-json"><a class="header-anchor" href="#包中的-package-json">¶</a>包中的 <code>package.json</code></h3>
+<p><a target="_blank" rel="noopener" href="https://nodejs.org/api/packages.html#name"><code>name</code> 字段</a>用于标识包。它在 workspace 中应该是唯一的。</p>
<blockquote>
<p>最佳做法是为<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/internal-packages">内部包</a>使用命名空间前缀,以避免与 npm 注册表上的其他包发生冲突。例如,如果组织名为 <code>clin</code>,则可以将包命名为 <code>@clin/package-name</code>。</p>
</blockquote>
@@ -238,10 +241,8 @@
<p><a target="_blank" rel="noopener" href="https://nodejs.org/api/packages.html#exports"><code>exports</code> 字段</a>用于指定要使用该包的其他包的入口点。如果要在另一个包中使用一个包中的代码,将从该入口点导入。</p>
<p>例如,如果有一个 <code>@repo/math</code> 包,则可以这么写 <code>exports</code> 字段:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;exports&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;.&quot;</span><span class="punctuation">:</span> <span class="string">&quot;./dist/constants.ts&quot;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;./add&quot;</span><span class="punctuation">:</span> <span class="string">&quot;./dist/add.ts&quot;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;./subtract&quot;</span><span class="punctuation">:</span> <span class="string">&quot;./dist/subtract.ts&quot;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
<p>然后就可以从 <code>@repo/math</code> 包中导入 <code>add</code> 和 <code>subtract</code> 函数了:</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="variable constant_">GRAVITATIONAL_CONSTANT</span>, <span class="variable constant_">SPEED_OF_LIGHT</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@repo/math&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; add &#125; <span class="keyword">from</span> <span class="string">&#x27;@repo/math/add&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; subtract &#125; <span class="keyword">from</span> <span class="string">&#x27;@repo/math/subtract&#x27;</span>;</span><br></pre></td></tr></table></figure>
-
<p>以这种方式使用导出有三个主要好处:</p>
<ul>
<li>避免 barrel 文件:barrel 文件是重新导出同一包中其他文件的文件,从而为整个包创建一个入口点。虽然它们可能看起来很方便,但编译器<a target="_blank" rel="noopener" href="https://vercel.com/blog/how-we-optimized-package-imports-in-next-js#what's-the-problem-with-barrel-files">和捆绑程序很难处理</a>它们,并且可能很快导致性能问题。</li>
@@ -253,12 +254,11 @@
<blockquote>
<p>其他:包通常使用 <code>src</code> 目录来存储其源代码并编译到 <code>dist</code> 目录(也应位于包中)。</p>
</blockquote>
-<h2 id="管理依赖项"><a href="#管理依赖项" class="headerlink" title="管理依赖项"></a>管理依赖项</h2><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;next&quot;</span><span class="punctuation">:</span> <span class="string">&quot;latest&quot;</span><span class="punctuation">,</span> <span class="comment">// 外部依赖</span></span><br><span class="line"> <span class="attr">&quot;@repo/ui&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*&quot;</span> <span class="comment">// 内部依赖</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
+<h2 id="管理依赖项"><a class="header-anchor" href="#管理依赖项">¶</a>管理依赖项</h2>
+<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;next&quot;</span><span class="punctuation">:</span> <span class="string">&quot;latest&quot;</span><span class="punctuation">,</span> <span class="comment">// 外部依赖</span></span><br><span class="line"> <span class="attr">&quot;@repo/ui&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*&quot;</span> <span class="comment">// 内部依赖</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
<p>在存储库中安装依赖项时,应将其直接安装在使用它的软件包中。包的 <code>package.json</code> 将包含所需的每个依赖项。外部和内部依赖项都是如此。</p>
<p>要在多个包中快速安装依赖项,可以:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install jest --workspace=web --workspace=@repo/ui --save-dev</span><br></pre></td></tr></table></figure>
-
<p>这种做法有几个好处:</p>
<ul>
<li>更清晰:当软件包的依赖项列在其 <code>package.json</code> 中时,更容易理解软件包所依赖的内容。在存储库中工作的开发人员可以一目了然地看到包中使用了哪些依赖项。</li>
@@ -269,94 +269,97 @@
<blockquote>
<p>属于工作区根目录的唯一依赖项是用于管理存储库的工具,而用于构建应用程序和库的依赖项安装在各自的包中。一些适合安装在根中的依赖项示例包括 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/turbo"><code>turbo</code></a>、<a target="_blank" rel="noopener" href="https://www.npmjs.com/package/husky"><code>husky</code></a> 或 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/lint-staged"><code>lint-staged</code></a>。</p>
</blockquote>
-<h3 id="保持同一版本的依赖"><a href="#保持同一版本的依赖" class="headerlink" title="保持同一版本的依赖"></a>保持同一版本的依赖</h3><p>一些 monorepo 维护者更喜欢按照规则在所有软件包中保持对相同版本的依赖关系。有几种方法可以实现此目的:</p>
+<h3 id="保持同一版本的依赖"><a class="header-anchor" href="#保持同一版本的依赖">¶</a>保持同一版本的依赖</h3>
+<p>一些 monorepo 维护者更喜欢按照规则在所有软件包中保持对相同版本的依赖关系。有几种方法可以实现此目的:</p>
<ul>
<li>使用专用的工具,比如 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/syncpack"><code>syncpack</code></a>、<a target="_blank" rel="noopener" href="https://www.npmjs.com/package/@manypkg/cli"><code>manypkg</code></a> 和 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/sherif"><code>sherif</code></a> 等工具可用于此特定目的。</li>
-<li>或者单纯的使用包管理器,可以使用软件包管理器通过一个命令更新依赖项版本:<ul>
+<li>或者单纯的使用包管理器,可以使用软件包管理器通过一个命令更新依赖项版本:
+<ul>
<li><code>npm install typescript@latest --workspaces</code></li>
</ul>
</li>
<li>或者最粗暴的用编辑器一次查找并替换存储库中所有 <code>package.json</code> 文件的依赖项版本。用 <code>“next”: “.*”</code> 之类的正则表达式来查找并替换为所需的版本。完成后再运行包管理器的 install 命令来更新 lock 文件.</li>
</ul>
-<h2 id="内部包"><a href="#内部包" class="headerlink" title="内部包"></a>内部包</h2><p><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/internal-packages">内部包</a>是工作区的构建块(building blocks),是一种在存储库中共享代码的强大方式。Turborepo 读取 <code>package.json</code> 中的依赖项来分析内部包之间的关系,并在后台创建 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#package-graph">Package Graph</a> 以优化存储库的工作流程。</p>
+<h2 id="内部包"><a class="header-anchor" href="#内部包">¶</a>内部包</h2>
+<p><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/internal-packages">内部包</a>是工作区的构建块(building blocks),是一种在存储库中共享代码的强大方式。Turborepo 读取 <code>package.json</code> 中的依赖项来分析内部包之间的关系,并在后台创建 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#package-graph">Package Graph</a> 以优化存储库的工作流程。</p>
<p>在创建内部包时,建议创建具有单一 “用途” 的包。这是最佳实践,具体取决于存储库的规模、组织、团队需求等。此策略具有以下几个优点:</p>
<ul>
<li>更易于理解:随着存储库的扩展,在存储库中工作的开发人员将能够更轻松地找到他们需要的代码。</li>
<li>减少每个包的依赖项:每个包使用更少的依赖项,以便 Turborepo 可以更有效地<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/prune">修剪包图的依赖项</a>。</li>
</ul>
-<p>在创建<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-types#application-packages">应用程序包</a>时,最好避免将共享代码放在这些包中。相反,应该为共享代码创建一个单独的包,并让应用程序包依赖于该包。<br>此外,应用程序包不应安装到其他包中。相反,应将它们视为 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#package-graph">Package Graph</a> 的入口点。</p>
-<h2 id="配置任务"><a href="#配置任务" class="headerlink" title="配置任务"></a>配置任务</h2><p>Turborepo 将始终按照 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/configuration"><code>turbo.json</code> 配置</a>和 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#package-graph">Package Graph</a> 中描述的顺序运行任务,并尽可能并行化工作以确保一切尽可能快地运行。</p>
+<p>在创建<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-types#application-packages">应用程序包</a>时,最好避免将共享代码放在这些包中。相反,应该为共享代码创建一个单独的包,并让应用程序包依赖于该包。<br>
+此外,应用程序包不应安装到其他包中。相反,应将它们视为 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#package-graph">Package Graph</a> 的入口点。</p>
+<h2 id="配置任务"><a class="header-anchor" href="#配置任务">¶</a>配置任务</h2>
+<p>Turborepo 将始终按照 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/configuration"><code>turbo.json</code> 配置</a>和 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#package-graph">Package Graph</a> 中描述的顺序运行任务,并尽可能并行化工作以确保一切尽可能快地运行。</p>
<p>根目录的 <code>turbo.json</code> 文件是注册 Turborepo 将运行的任务的位置。定义任务后,将能够使用 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/run"><code>turbo run</code></a> 运行一个或多个任务。</p>
<p><code>tasks</code> 对象中的每个 key 都是一个可以通过 <code>turbo run</code> 执行的任务。Turborepo 将在 <code>package.json</code> 中搜索与任务同名的软件包:</p>
<p><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/configuration#dependson"><code>dependsOn</code> 键</a>用于指定在其他任务开始运行之前必须完成的任务。在大多数情况下,库的<code>build</code>脚本在应用程序的<code>build</code>脚本运行之前完成,所以可以这么写:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dependsOn&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;^build&quot;</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
<blockquote>
-<p><code>^</code> 这个语法告诉 Turborepo 从依赖关系图的底部开始运行任务。如果应用程序依赖于名为 <code>ui</code> 的库,并且该库具有<code>build</code>任务,则 <code>ui</code> 中的<code>build</code>脚本将首先运行。成功完成后,才会运行应用程序中的<code>build</code>任务。<br>这是一个重要的形式,因为它可以确保应用程序的<code>build</code>任务具有编译所需的所有必要依赖项。当依赖关系图发展到具有多个级别的任务依赖关系的更复杂的结构时,此概念也适用。</p>
+<p><code>^</code> 这个语法告诉 Turborepo 从依赖关系图的底部开始运行任务。如果应用程序依赖于名为 <code>ui</code> 的库,并且该库具有<code>build</code>任务,则 <code>ui</code> 中的<code>build</code>脚本将首先运行。成功完成后,才会运行应用程序中的<code>build</code>任务。<br>
+这是一个重要的形式,因为它可以确保应用程序的<code>build</code>任务具有编译所需的所有必要依赖项。当依赖关系图发展到具有多个级别的任务依赖关系的更复杂的结构时,此概念也适用。</p>
</blockquote>
<p>有时可能需要确保同一包中的两个任务按特定顺序运行。例如需要先在库中运行<code>build</code>任务,然后再在同一库中运行<code>test</code>任务。这种情况删掉 <code>^</code> 就行了:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dependsOn&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;build&quot;</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
<p>还可以在特定包中指定要依赖的单个任务。例如在任何 <code>lint</code> 任务之前运行 <code>utils</code> 中的<code>build</code>任务:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;lint&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dependsOn&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;utils#build&quot;</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
<p>或者更加细致的限定 <code>lint</code>:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;web#lint&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dependsOn&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;utils#build&quot;</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
<p>即 <code>Web 包中的</code> <code>lint</code> 任务只能在 <code>utils</code> 包中的<code>build</code>任务完成后运行。</p>
<p>某些任务可能没有任何依赖项。例如用于在 Markdown 文件中查找拼写错误的任务可能不需要关心其他任务的状态。在这种情况下,省略 <code>dependsOn</code> 键或给个空数组就行了:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;spell-check&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dependsOn&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
-<h3 id="指定输入输出"><a href="#指定输入输出" class="headerlink" title="指定输入输出"></a>指定输入输出</h3><p><code>outputs</code> 键告诉 Turborepo 文件和目录在任务成功完成时应该缓存在哪。如果未定义此 key,Turborepo 将不会缓存任何文件。</p>
+<h3 id="指定输入输出"><a class="header-anchor" href="#指定输入输出">¶</a>指定输入输出</h3>
+<p><code>outputs</code> 键告诉 Turborepo 文件和目录在任务成功完成时应该缓存在哪。如果未定义此 key,Turborepo 将不会缓存任何文件。</p>
<p>例如缓存 vite 的输出一般可以这么写:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;outputs&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;dist/&quot;</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
<p><code>inputs</code> 键用于指定要包含在任务哈希中以进行<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/crafting-your-repository/caching">缓存</a>的文件。默认情况下,Turborepo 将包含包中由 Git 跟踪的所有文件。但是也可以使用 <code>inputs</code> 键更具体地说明哈希中包含哪些文件, 例如,在 Markdown 文件中查找拼写错误的任务可以定义如下::</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;spell-check&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;inputs&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;/*.md&quot;</span><span class="punctuation">,</span> <span class="string">&quot;/*.mdx&quot;</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
<p>可以通过微调 <code>input</code> 以忽略对已知不会影响任务输出的文件的更改来提高某些任务的缓存命中率, 可以使用 <code>$TURBO_DEFAULT$</code> 微语法来微调默认 <code>input</code> 行为:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;inputs&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;$TURBO_DEFAULT$&quot;</span><span class="punctuation">,</span> <span class="string">&quot;!README.md&quot;</span><span class="punctuation">]</span> </span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
<p>这里 Turborepo 使用<code>build</code>任务的默认<code>input</code>,但会忽略对 <code>README.md</code> 文件的更改。如果 <code>README.md</code> 文件发生更改,任务仍将用上缓存。</p>
-<h3 id="Root-任务"><a href="#Root-任务" class="headerlink" title="Root 任务"></a>Root 任务</h3><p>还可以使用 <code>turbo</code> 在 Workspace 根的 <code>package.json</code>中运行脚本。例如,除了每个软件包中的 <code>lint</code> 任务外,可能还需要对 Workspace 根目录中的文件运行 <code>lint:root</code> 任务:</p>
+<h3 id="Root-任务"><a class="header-anchor" href="#Root-任务">¶</a>Root 任务</h3>
+<p>还可以使用 <code>turbo</code> 在 Workspace 根的 <code>package.json</code>中运行脚本。例如,除了每个软件包中的 <code>lint</code> 任务外,可能还需要对 Workspace 根目录中的文件运行 <code>lint:root</code> 任务:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;lint&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dependsOn&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;^lint&quot;</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;//#lint:root&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</span> </span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
-<h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><ul>
-<li><p><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/package-configurations">包配置</a>是直接放入包中的<code>turbo.json</code>文件。这允许软件包为其自己的任务定义特定行为,而不会影响存储库的其余部分。</p>
-</li>
-<li><p>有一些始终需要运行的任务,例如缓存生成后的部署脚本。对于这些任务,用 <code>“cache”: false</code> :</p>
+<h3 id="其他"><a class="header-anchor" href="#其他">¶</a>其他</h3>
+<ul>
+<li><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/package-configurations">包配置</a>是直接放入包中的<code>turbo.json</code>文件。这允许软件包为其自己的任务定义特定行为,而不会影响存储库的其余部分。</li>
+<li>有一些始终需要运行的任务,例如缓存生成后的部署脚本。对于这些任务,用 <code>“cache”: false</code> :</li>
+</ul>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;deploy&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dependsOn&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;^build&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;cache&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line"> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;outputs&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;dist/&quot;</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-</li>
-<li><p>某些任务可以并行运行,例如 Linter 不需要等待依赖项中的输出成功才能运行:</p>
+<ul>
+<li>某些任务可以并行运行,例如 Linter 不需要等待依赖项中的输出成功才能运行:</li>
+</ul>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;transit&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dependsOn&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;^transit&quot;</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;check-types&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dependsOn&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;transit&quot;</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>这里用到了 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#transit-nodes">Transit Nodes</a> (就是名为 <code>transit</code> 的任务),这些 Transit Node 使用不执行任何操作的任务在软件包依赖项之间创建关系,这里用了名称 <code>transit</code>,但可以将任务命名为 Workspace 中尚未包含脚本的任何名称。</p>
</blockquote>
-</li>
-</ul>
-<h2 id="正在运行的任务"><a href="#正在运行的任务" class="headerlink" title="正在运行的任务"></a>正在运行的任务</h2><p>当在软件包的目录中时,<code>turbo</code> 会自动将命令范围限定为该软件包的 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#package-graph">Package Graph</a>:</p>
+<h2 id="正在运行的任务"><a class="header-anchor" href="#正在运行的任务">¶</a>正在运行的任务</h2>
+<p>当在软件包的目录中时,<code>turbo</code> 会自动将命令范围限定为该软件包的 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#package-graph">Package Graph</a>:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd apps/docs</span><br><span class="line">turbo build</span><br></pre></td></tr></table></figure>
<p>将使用 <code>turbo.json</code> 中注册的<code>build</code>任务运行 <code>docs</code> 包的<code>build</code>任务。</p>
<blockquote>
<p>但也可以<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/crafting-your-repository/running-tasks#using-filters">使用过滤器</a>覆盖 Automatic Package Scoping。</p>
</blockquote>
-<h3 id="运行多个任务"><a href="#运行多个任务" class="headerlink" title="运行多个任务"></a>运行多个任务</h3><p><code>Turbo</code> 能够运行多个任务,并尽可能并行化:</p>
+<h3 id="运行多个任务"><a class="header-anchor" href="#运行多个任务">¶</a>运行多个任务</h3>
+<p><code>Turbo</code> 能够运行多个任务,并尽可能并行化:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">turbo run build test lint check-types</span><br></pre></td></tr></table></figure>
-
-<h2 id="缓存"><a href="#缓存" class="headerlink" title="缓存"></a>缓存</h2><p>Turborepo 的缓存在本地工作时可以节省大量时间 - 启用<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/remote-caching">远程缓存</a>时,它的功能更加强大,可在整个团队和 CI 之间共享缓存。</p>
-<h3 id="缓存什么?"><a href="#缓存什么?" class="headerlink" title="缓存什么?"></a>缓存什么?</h3><ul>
+<h2 id="缓存"><a class="header-anchor" href="#缓存">¶</a>缓存</h2>
+<p>Turborepo 的缓存在本地工作时可以节省大量时间 - 启用<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/remote-caching">远程缓存</a>时,它的功能更加强大,可在整个团队和 CI 之间共享缓存。</p>
+<h3 id="缓存什么?"><a class="header-anchor" href="#缓存什么?">¶</a>缓存什么?</h3>
+<ul>
<li>在 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/configuration#outputs"><code>turbo.json 的 outputs</code> 键</a>中定义的任务的文件输出。</li>
<li>任务的终端输出,从任务第一次运行时开始将这些日志恢复到终端。</li>
<li>对输入进行哈希处理,为任务运行创建一个 “fingerprints”。当 “fingerprints” 匹配时,运行任务将命中缓存。</li>
</ul>
-<h3 id="其他-1"><a href="#其他-1" class="headerlink" title="其他"></a>其他</h3><ul>
+<h3 id="其他-v2"><a class="header-anchor" href="#其他-v2">¶</a>其他</h3>
+<ul>
<li><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/run#--dry----dry-run"><code>--dry</code> 标志</a>,可用于查看如果在没有实际运行任务的情况下运行任务会发生什么。当不确定正在运行的任务时,这对于调试缓存问题非常有用。</li>
<li><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/run#--summarize"><code>--summarize</code> 标志</a>,可用于获取任务的所有输入、输出等的概览。比较两个摘要将揭示两个任务的哈希值不同的原因。</li>
<li>强制 <code>turbo</code> 重新执行已缓存的任务,请使用 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/run#--force"><code>--force</code> 标志</a>。请注意,这将禁用读取缓存,而不是写入。</li>
</ul>
-<h2 id="开发工作流"><a href="#开发工作流" class="headerlink" title="开发工作流"></a>开发工作流</h2><p>在 <code>turbo.json</code> 中定义开发任务 (development task) 会告诉 Turborepo 将运行一个长期任务。这对于运行开发服务器、运行测试或构建应用程序等操作非常有用:</p>
+<h2 id="开发工作流"><a class="header-anchor" href="#开发工作流">¶</a>开发工作流</h2>
+<p>在 <code>turbo.json</code> 中定义开发任务 (development task) 会告诉 Turborepo 将运行一个长期任务。这对于运行开发服务器、运行测试或构建应用程序等操作非常有用:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;cache&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;persistent&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
<ul>
<li><code>&quot;cache&quot;: false</code>:告诉 Turborepo 不要尝试缓存任务的结果。由于这是一项开发任务,可能会频繁更改代码,因此缓存结果没有用。</li>
<li><code>&quot;persistent&quot;: true</code>:告诉 Turborepo 保持任务运行,直到停止它。此键用作终端 UI 的信号,用于将任务视为长时间运行和交互式任务。此外,它还可以防止意外依赖不会退出的任务。</li>
@@ -364,13 +367,13 @@
<p>一些脚本允许使用 <code>stdin</code> 在其中键入以进行交互式输入。使用<a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/configuration#ui">终端 UI</a>,可以选择一个任务,输入它,然后像往常一样使用 <code>stdin</code>。</p>
<p>需要运行用于设置开发环境或预构建包的脚本。可以使用 <code>dependsOn</code> 确保这些任务在 <code>dev</code> 任务之前运行:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;cache&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;persistent&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;dependsOn&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;//#dev:setup&quot;</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;//#dev:setup&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;outputs&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;.codegen/&quot;</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
<p>这里用的是 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/crafting-your-repository/configuring-tasks#registering-root-tasks">Root Task</a>,但可以对 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/crafting-your-repository/configuring-tasks#depending-on-a-specific-task-in-a-specific-package">packages 中的任意任务</a>使用相同的思路。</p>
-<h3 id="Watch-mode"><a href="#Watch-mode" class="headerlink" title="Watch mode"></a>Watch mode</h3><p>许多工具都有一个内置的 watcher,比如 <a target="_blank" rel="noopener" href="https://www.typescriptlang.org/docs/handbook/compiler-options.html#compiler-options"><code>tsc --watch</code></a>,它会响应源代码中的更改。有些则没有,<code>Turbo Watch</code> 为任何工具添加了依赖项感知的 Watcher。对源代码的更改将遵循在 <code>turbo.json</code> 中描述的 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#task-graph">Task Graph (任务图</a>),例如:</p>
+<h3 id="Watch-mode"><a class="header-anchor" href="#Watch-mode">¶</a>Watch mode</h3>
+<p>许多工具都有一个内置的 watcher,比如 <a target="_blank" rel="noopener" href="https://www.typescriptlang.org/docs/handbook/compiler-options.html#compiler-options"><code>tsc --watch</code></a>,它会响应源代码中的更改。有些则没有,<code>Turbo Watch</code> 为任何工具添加了依赖项感知的 Watcher。对源代码的更改将遵循在 <code>turbo.json</code> 中描述的 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts/package-and-task-graph#task-graph">Task Graph (任务图</a>),例如:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;persistent&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;cache&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line"> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;lint&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;dependsOn&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;^lint&quot;</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-
<p>当运行 <code>turbo watch dev lint</code> 时,会看到每当更改源代码时,<code>lint</code> 脚本都会重新运行,尽管 ESLint 没有内置的 watcher。<code>Turbo Watch</code> 还知道内部依赖关系,因此 <code>@repo/UI</code> 中的代码更改将在 <code>@repo/UI</code> 和 <code>Web</code> 中重新运行任务。</p>
-<h2 id="环境变量"><a href="#环境变量" class="headerlink" title="环境变量"></a>环境变量</h2><p>Turborepo 需要根据环境变量来决定是否改变应用程序的行为。在 <code>turbo.json</code> 文件中使用 <code>env</code> 和 <code>globalEnv</code> 键:</p>
+<h2 id="环境变量"><a class="header-anchor" href="#环境变量">¶</a>环境变量</h2>
+<p>Turborepo 需要根据环境变量来决定是否改变应用程序的行为。在 <code>turbo.json</code> 文件中使用 <code>env</code> 和 <code>globalEnv</code> 键:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;globalEnv&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;IMPORTANT_GLOBAL_VARIABLE&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;env&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;MY_API_URL&quot;</span><span class="punctuation">,</span> <span class="string">&quot;MY_API_KEY&quot;</span><span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
<ul>
<li><code>globalEnv</code>:更改此列表中任何环境变量的值都将更改所有任务的哈希值。</li>
@@ -379,29 +382,31 @@
<blockquote>
<p>Turborepo 会自动将前缀通配符添加到常见框架的 <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/configuration#env"><code>env</code></a> 键中 (### <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/crafting-your-repository/using-environment-variables#framework-inference">Framework Inference</a>)</p>
</blockquote>
-<h3 id="Environment-mode"><a href="#Environment-mode" class="headerlink" title="Environment mode"></a>Environment mode</h3><p>Turborepo 的 Environment Mode 允许控制哪些环境变量在运行时可用于任务:</p>
+<h3 id="Environment-mode"><a class="header-anchor" href="#Environment-mode">¶</a>Environment mode</h3>
+<p>Turborepo 的 Environment Mode 允许控制哪些环境变量在运行时可用于任务:</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/crafting-your-repository/using-environment-variables#strict-mode">严格模式</a>(默认):将环境变量过滤为仅在 <code>turbo.json</code> 的 <code>env</code> 和 <code>globalEnv</code> 键中指定的环境变量。</li>
<li><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/crafting-your-repository/using-environment-variables#loose-mode">宽松模式</a>:允许进程的所有环境变量可用。</li>
</ul>
-<h3 id="其他-2"><a href="#其他-2" class="headerlink" title="其他"></a>其他</h3><ul>
-<li><p><code>.env</code> 文件非常适合在本地处理应用程序。Turborepo 不会将 .env 文件加载到任务的运行时中,而是让它们由框架或 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/dotenv"><code>dotenv</code></a> 等工具处理。但是,<code>turbo</code> 必须知道 <code>.env</code> 文件中值的更改,以便它可以将它们用于哈希。如果在两次构建之间更改 <code>.env</code> 文件中的变量,则 <code>build</code> 任务应该不会用上缓存。所以可以将其添加到 <code>input</code> 键中:</p>
+<h3 id="其他-v3"><a class="header-anchor" href="#其他-v3">¶</a>其他</h3>
+<ul>
+<li><code>.env</code> 文件非常适合在本地处理应用程序。Turborepo 不会将 .env 文件加载到任务的运行时中,而是让它们由框架或 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/dotenv"><code>dotenv</code></a> 等工具处理。但是,<code>turbo</code> 必须知道 <code>.env</code> 文件中值的更改,以便它可以将它们用于哈希。如果在两次构建之间更改 <code>.env</code> 文件中的变量,则 <code>build</code> 任务应该不会用上缓存。所以可以将其添加到 <code>input</code> 键中:</li>
+</ul>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;globalDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;.env&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="comment">// All task hashes</span></span><br><span class="line"> <span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;inputs&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;$TURBO_DEFAULT$&quot;</span><span class="punctuation">,</span> <span class="string">&quot;.env&quot;</span><span class="punctuation">,</span> <span class="string">&quot;.env.local&quot;</span><span class="punctuation">]</span> <span class="comment">// Only the `build` task hash</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>
-</li>
-<li><p>不建议在存储库的根目录中使用 <code>.env</code> 文件。相反,建议将 <code>.env</code> 文件放入使用它们的包中。</p>
-</li>
-<li><p><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/eslint-config-turbo"><code>eslint-config-turbo</code> 软件包</a>可帮助查找代码中使用但未在 <code>turbo.json</code>中列出的环境变量。这有助于确保在配置中考虑所有环境变量。</p>
-</li>
-<li><p>Turborepo 在任务开始时对任务的环境变量进行哈希处理。如果在任务期间创建或更改环境变量,Turborepo 将不知道这些更改,也不会在任务哈希中考虑这些更改。</p>
-</li>
+<ul>
+<li>不建议在存储库的根目录中使用 <code>.env</code> 文件。相反,建议将 <code>.env</code> 文件放入使用它们的包中。</li>
+<li><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/reference/eslint-config-turbo"><code>eslint-config-turbo</code> 软件包</a>可帮助查找代码中使用但未在 <code>turbo.json</code>中列出的环境变量。这有助于确保在配置中考虑所有环境变量。</li>
+<li>Turborepo 在任务开始时对任务的环境变量进行哈希处理。如果在任务期间创建或更改环境变量,Turborepo 将不知道这些更改,也不会在任务哈希中考虑这些更改。</li>
</ul>
<hr>
-<h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><ul>
+<h2 id="最后"><a class="header-anchor" href="#最后">¶</a>最后</h2>
+<ul>
<li><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/crafting-your-repository/constructing-ci">配置 CI</a></li>
<li><a target="_blank" rel="noopener" href="https://turbo.build/repo/docs/core-concepts">核心概念</a></li>
</ul>
<blockquote>
-<p>本快速入门文档参照 Turborepo 2.x 官方文档: <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs">https://turbo.build/repo/docs</a><br>最后一次编辑:二〇二四年九月二十七日下午六点〇七分</p>
+<p>本快速入门文档参照 Turborepo 2.x 官方文档: <a target="_blank" rel="noopener" href="https://turbo.build/repo/docs">https://turbo.build/repo/docs</a><br>
+最后一次编辑:二〇二四年九月二十七日下午六点〇七分</p>
</blockquote>
</div>